summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2024-11-07 11:44:54 +0100
committerDavid Lönnhager <david.l@mullvad.net>2024-11-07 11:44:54 +0100
commitf19d71f6bee7702448e34457de7f0d8089480fa8 (patch)
tree63287a4186e014775a1cc5e2c9533c7b090060fc
parentcbf3ed87d72077ad10efb2b45e93f6f68486ae99 (diff)
parenta08f093e933f0353612ec65a2de165bfd2984fbb (diff)
downloadmullvadvpn-f19d71f6bee7702448e34457de7f0d8089480fa8.tar.xz
mullvadvpn-f19d71f6bee7702448e34457de7f0d8089480fa8.zip
Merge branch 'add-hyperv-blocking-rules'
-rw-r--r--CHANGELOG.md7
-rw-r--r--Cargo.lock84
-rw-r--r--docs/known-issues.md31
-rw-r--r--docs/security.md1
-rw-r--r--talpid-core/Cargo.toml4
-rw-r--r--talpid-core/src/firewall/mod.rs2
-rw-r--r--talpid-core/src/firewall/windows/hyperv.rs191
-rw-r--r--talpid-core/src/firewall/windows/mod.rs (renamed from talpid-core/src/firewall/windows.rs)97
8 files changed, 398 insertions, 19 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c92760d180..0f46c72c44 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -48,6 +48,13 @@ Line wrap the file at 100 chars. Th
#### macOS
- Fix packets being duplicated on LAN when split tunneling is enabled.
+### Security
+#### Windows
+- Block WSL/Hyper-V traffic in secured states (except the connected state). The normal firewall
+ (WFP) filters normally do not apply for VMs. This mitigates the issue by ensuring that it does not
+ leak (as easily) when the VPN tunnel is up. Previously, WSL would leak while in the blocked or
+ connecting state, or while lockdown mode was active.
+
## [2024.7] - 2024-10-30
This release is identical to 2024.7-beta1.
diff --git a/Cargo.lock b/Cargo.lock
index 6d86987510..b89c283c87 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1604,7 +1604,7 @@ dependencies = [
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
- "windows-core",
+ "windows-core 0.52.0",
]
[[package]]
@@ -4165,9 +4165,12 @@ dependencies = [
"tun",
"which",
"widestring",
+ "windows",
+ "windows-core 0.58.0",
"windows-service",
"windows-sys 0.52.0",
"winreg 0.51.0",
+ "wmi",
]
[[package]]
@@ -5058,6 +5061,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
+name = "windows"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
+dependencies = [
+ "windows-core 0.58.0",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5067,6 +5080,50 @@ dependencies = [
]
[[package]]
+name = "windows-core"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-result",
+ "windows-strings",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
name = "windows-service"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5078,6 +5135,16 @@ dependencies = [
]
[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
name = "windows-sys"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5375,6 +5442,21 @@ dependencies = [
]
[[package]]
+name = "wmi"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbdda506bdee26ba617bd814538b690e14f59e8185345344cff113a8be21c005"
+dependencies = [
+ "chrono",
+ "futures",
+ "log",
+ "serde",
+ "thiserror",
+ "windows",
+ "windows-core 0.58.0",
+]
+
+[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/docs/known-issues.md b/docs/known-issues.md
index 7e614bb596..8cdcd3f52e 100644
--- a/docs/known-issues.md
+++ b/docs/known-issues.md
@@ -169,14 +169,24 @@ app. We have observed it on macOS 14.6 and newer, but it could very well have ex
The Hyper-V Virtual Ethernet Adapter passes traffic to and from guests without letting the
host’s firewall inspect the packets in the same way normal packets are inspected.
The forwarded (NATed) packets are seen in the lower layers of WFP (OSI layer 2) as
-Ethernet frames only. This means that all firewall rules inserted by the Mullvad app
+Ethernet frames only. This means that all the normal firewall rules inserted by the Mullvad app
to stop leaks are circumvented.
-This affects all virtual machines, containers and software running on a Hyper-V virtual network.
+This problem affects all virtual machines, containers and software running on a Hyper-V virtual
+network.
-We currently have no fix for this issue. We have been experimenting with simply blocking all
-layer 2 traffic. This solution would be safer, but at the same time break some software. The
-user can instead choose to not use said software.
+The app mitigates the issue by blocking all Hyper-V traffic in secured states using Hyper-V-specific
+filters, i.e. a firewall that applies specifically to the Hyper-V hypervisor. The connected state is
+exempted since the routing table will ensure that traffic is tunneled in that case, at least for WSL
+(see details below).
+
+There are certain limitations to this mitigation. First, the Hyper-V firewall is only available on
+*Windows 11 version 22H2 and above*, so it has no effect on earlier versions of Windows.
+Additionally, LAN traffic will never be blocked while connected, regardless of whether "Local
+network sharing" is enabled. Moreover, DNS leaks are more likely to occur.
+
+Your [WSL config] needs to enable the `firewall` setting for the Hyper-V firewall to be enabled.
+It is enabled by default.
#### Linux under WSL2
@@ -184,12 +194,7 @@ Network traffic from a Linux guest running under WSL2 always goes out the defaul
the host machine without being inspected by the normal layers of WFP (the firewall on the
Windows host that Mullvad use to prevent leaks). This means that if there is a VPN tunnel
up and running, the Linux guest’s traffic will be sent via the VPN with no leaks!
-However, if there is no active VPN tunnel, as is the case when the app is disconnected,
-connecting, reconnecting, or blocking (after an error occurred) then the Linux guest’s
-traffic will leak out on the regular network, even if “Lockdown mode” is enabled.
-
-WSL1 does not have this issue. So if you need to prevent leaks and you also need to use
-Linux on Windows, you can try using it under WSL1 instead.
+In the other states, the mitigation above is used to prevent leaks.
#### Edge using Application guard
@@ -197,13 +202,15 @@ When running the Microsoft Edge browser with Microsoft Defender Application Guar
the browser uses Hyper-V networking underneath. This makes the network traffic generated
by the browser ignore the Mullvad firewall rules. On top of this, it even ignores the routing
table, and *always* send the traffic directly on the physical network interface
-instead of the tunnel interface.
+instead of the tunnel interface. Hence, the mitigation above is ineffective when the VPN tunnel is
+active.
This affects all app versions and all versions of Edge on Application Guard as far as we know.
Since [Application Guard is deprecated] we are not going to put much effort into solving this.
We recommend users to not use Application Guard.
[Application Guard is deprecated]: https://learn.microsoft.com/en-us/deployedge/microsoft-edge-security-windows-defender-application-guard
+[WSL config]: https://learn.microsoft.com/en-us/windows/wsl/wsl-config#main-wsl-settings
#### Other VPN software
diff --git a/docs/security.md b/docs/security.md
index c79efa2598..6acd412c14 100644
--- a/docs/security.md
+++ b/docs/security.md
@@ -313,7 +313,6 @@ removed.
### Windows
-
On Windows, persistent firewall filters may be added when the service exits, in case the service
decides to continue to enforce a blocking policy. These filters block any traffic occurring before
the service has started back up again during boot, including before the BFE service has started.
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index 3207d485cb..690d5797cc 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -71,6 +71,10 @@ once_cell = { workspace = true }
windows-service = "0.6.0"
talpid-windows = { path = "../talpid-windows" }
+wmi = "0.14.0"
+windows-core = "0.58.0"
+windows = "0.58.0"
+
[target.'cfg(windows)'.dependencies.windows-sys]
workspace = true
features = [
diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs
index 0934972809..0bcbf3191e 100644
--- a/talpid-core/src/firewall/mod.rs
+++ b/talpid-core/src/firewall/mod.rs
@@ -17,7 +17,7 @@ mod imp;
mod imp;
#[cfg(windows)]
-#[path = "windows.rs"]
+#[path = "windows/mod.rs"]
mod imp;
#[cfg(target_os = "android")]
diff --git a/talpid-core/src/firewall/windows/hyperv.rs b/talpid-core/src/firewall/windows/hyperv.rs
new file mode 100644
index 0000000000..1fc8bb2578
--- /dev/null
+++ b/talpid-core/src/firewall/windows/hyperv.rs
@@ -0,0 +1,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_rule`]. 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))
+ }
+}
diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows/mod.rs
index 0ee5a81ca2..d4b0b883d4 100644
--- a/talpid-core/src/firewall/windows.rs
+++ b/talpid-core/src/firewall/windows/mod.rs
@@ -1,16 +1,49 @@
use crate::{dns::ResolvedDnsConfig, tunnel::TunnelMetadata};
-use std::{ffi::CStr, io, net::IpAddr, ptr};
+use std::{ffi::CStr, io, net::IpAddr, ptr, sync::LazyLock};
use self::winfw::*;
use super::{FirewallArguments, FirewallPolicy, InitialFirewallState};
use talpid_types::{
net::{AllowedEndpoint, AllowedTunnelTraffic},
tunnel::FirewallPolicyError,
+ ErrorExt,
};
use widestring::WideCString;
use windows_sys::Win32::Globalization::{MultiByteToWideChar, CP_ACP};
+mod hyperv;
+
+const HYPERV_LEAK_WARNING_MSG: &str = "Hyper-V (e.g. WSL machines) may leak in blocked states.";
+
+// `COMLibrary` must be initialized for per thread, so use TLS
+thread_local! {
+ static WMI: Option<wmi::WMIConnection> = {
+ let result = hyperv::init_wmi();
+ if matches!(&result, Err(hyperv::Error::ObtainHyperVClass(_))) {
+ log::warn!("The Hyper-V firewall is not available. {HYPERV_LEAK_WARNING_MSG}");
+ return None;
+ }
+ consume_and_log_hyperv_err(
+ "Initialize COM and WMI",
+ result,
+ )
+ };
+}
+
+/// Enable or disable blocking Hyper-V rule
+static BLOCK_HYPERV: LazyLock<bool> = LazyLock::new(|| {
+ let enable = std::env::var("TALPID_FIREWALL_BLOCK_HYPERV")
+ .map(|v| v != "0")
+ .unwrap_or(true);
+
+ if !enable {
+ log::debug!("Hyper-V block rule disabled by TALPID_FIREWALL_BLOCK_HYPERV");
+ }
+
+ enable
+});
+
/// Errors that can happen when configuring the Windows firewall.
#[derive(thiserror::Error, Debug)]
pub enum Error {
@@ -44,7 +77,7 @@ const WINFW_TIMEOUT_SECONDS: u32 = 5;
const LOGGING_CONTEXT: &[u8] = b"WinFw\0";
-/// The Windows implementation for the firewall and DNS.
+/// The Windows implementation for the firewall.
pub struct Firewall(());
impl Firewall {
@@ -87,11 +120,22 @@ impl Firewall {
.into_result()?
};
log::trace!("Successfully initialized windows firewall module to a blocking state");
+
+ with_wmi_if_enabled(|wmi| {
+ let result = hyperv::add_blocking_hyperv_firewall_rules(wmi);
+ consume_and_log_hyperv_err("Add block-all Hyper-V filter", result);
+ });
+
Ok(Firewall(()))
}
pub fn apply_policy(&mut self, policy: FirewallPolicy) -> Result<(), Error> {
- match policy {
+ let should_block_hyperv = matches!(
+ policy,
+ FirewallPolicy::Connecting { .. } | FirewallPolicy::Blocked { .. }
+ );
+
+ let apply_result = match policy {
FirewallPolicy::Connecting {
peer_endpoint,
tunnel,
@@ -128,11 +172,29 @@ impl Firewall {
allowed_endpoint.map(WinFwAllowedEndpointContainer::from),
)
}
- }
+ };
+
+ with_wmi_if_enabled(|wmi| {
+ if should_block_hyperv {
+ let result = hyperv::add_blocking_hyperv_firewall_rules(wmi);
+ consume_and_log_hyperv_err("Add block-all Hyper-V filter", result);
+ } else {
+ let result = hyperv::remove_blocking_hyperv_firewall_rules(wmi);
+ consume_and_log_hyperv_err("Remove block-all Hyper-V filter", result);
+ }
+ });
+
+ apply_result
}
pub fn reset_policy(&mut self) -> Result<(), Error> {
unsafe { WinFw_Reset().into_result().map_err(Error::ResettingPolicy) }?;
+
+ with_wmi_if_enabled(|wmi| {
+ let result = hyperv::remove_blocking_hyperv_firewall_rules(wmi);
+ consume_and_log_hyperv_err("Remove block-all Hyper-V filter", result);
+ });
+
Ok(())
}
@@ -435,6 +497,33 @@ fn multibyte_to_wide(mb_string: &CStr, codepage: u32) -> Result<Vec<u16>, io::Er
Ok(wc_buffer)
}
+// Convert `result` into an option and log the error, if any.
+fn consume_and_log_hyperv_err<T>(
+ action: &'static str,
+ result: Result<T, hyperv::Error>,
+) -> Option<T> {
+ result
+ .inspect_err(|error| {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg(&format!("{action}. {HYPERV_LEAK_WARNING_MSG}"))
+ );
+ })
+ .ok()
+}
+
+// Run a closure with the current thread's WMI connection, if available
+fn with_wmi_if_enabled(f: impl FnOnce(&wmi::WMIConnection)) {
+ if !*BLOCK_HYPERV {
+ return;
+ }
+ WMI.with(|wmi| {
+ if let Some(con) = wmi {
+ f(con)
+ }
+ })
+}
+
#[allow(non_snake_case)]
mod winfw {
use super::{widestring_ip, AllowedEndpoint, AllowedTunnelTraffic, Error, WideCString};