mirror of
https://mirror.suhoan.cn/https://github.com/EasyTier/EasyTier.git
synced 2025-12-12 04:37:23 +08:00
add windows firewall for tun interface (#1130)
allow all icmp/tcp/udp on tun interface.
This commit is contained in:
@@ -7,8 +7,9 @@ use windows::{
|
||||
Win32::{
|
||||
Foundation::{BOOL, FALSE},
|
||||
NetworkManagement::WindowsFirewall::{
|
||||
INetFwPolicy2, INetFwRule, NET_FW_ACTION_ALLOW, NET_FW_PROFILE2_PRIVATE,
|
||||
NET_FW_PROFILE2_PUBLIC, NET_FW_RULE_DIR_IN, NET_FW_RULE_DIR_OUT,
|
||||
INetFwPolicy2, INetFwRule, NET_FW_ACTION_ALLOW, NET_FW_PROFILE2_DOMAIN,
|
||||
NET_FW_PROFILE2_PRIVATE, NET_FW_PROFILE2_PUBLIC, NET_FW_RULE_DIR_IN,
|
||||
NET_FW_RULE_DIR_OUT,
|
||||
},
|
||||
Networking::WinSock::{
|
||||
htonl, setsockopt, WSAGetLastError, WSAIoctl, IPPROTO_IP, IPPROTO_IPV6,
|
||||
@@ -164,7 +165,7 @@ impl Drop for ComInitializer {
|
||||
|
||||
pub fn do_add_self_to_firewall_allowlist(inbound: bool) -> anyhow::Result<()> {
|
||||
let _com = ComInitializer::new()?;
|
||||
// 创建防火墙策略实例
|
||||
// Create firewall policy instance
|
||||
let policy: INetFwPolicy2 = unsafe {
|
||||
CoCreateInstance(
|
||||
&windows::Win32::NetworkManagement::WindowsFirewall::NetFwPolicy2,
|
||||
@@ -173,7 +174,7 @@ pub fn do_add_self_to_firewall_allowlist(inbound: bool) -> anyhow::Result<()> {
|
||||
)
|
||||
}?;
|
||||
|
||||
// 创建防火墙规则实例
|
||||
// Create firewall rule instance
|
||||
let rule: INetFwRule = unsafe {
|
||||
CoCreateInstance(
|
||||
&windows::Win32::NetworkManagement::WindowsFirewall::NetFwRule,
|
||||
@@ -182,7 +183,7 @@ pub fn do_add_self_to_firewall_allowlist(inbound: bool) -> anyhow::Result<()> {
|
||||
)
|
||||
}?;
|
||||
|
||||
// 设置规则属性
|
||||
// Set rule properties
|
||||
let exe_path = std::env::current_exe()
|
||||
.with_context(|| "Failed to get current executable path when adding firewall rule")?
|
||||
.to_string_lossy()
|
||||
@@ -202,17 +203,19 @@ pub fn do_add_self_to_firewall_allowlist(inbound: bool) -> anyhow::Result<()> {
|
||||
rule.SetApplicationName(&app_path)?;
|
||||
rule.SetAction(NET_FW_ACTION_ALLOW)?;
|
||||
if inbound {
|
||||
rule.SetDirection(NET_FW_RULE_DIR_IN)?; // 允许入站连接
|
||||
rule.SetDirection(NET_FW_RULE_DIR_IN)?; // Allow inbound connections
|
||||
} else {
|
||||
rule.SetDirection(NET_FW_RULE_DIR_OUT)?; // 允许出站连接
|
||||
rule.SetDirection(NET_FW_RULE_DIR_OUT)?; // Allow outbound connections
|
||||
}
|
||||
rule.SetEnabled(windows::Win32::Foundation::VARIANT_TRUE)?;
|
||||
rule.SetProfiles(NET_FW_PROFILE2_PRIVATE.0 | NET_FW_PROFILE2_PUBLIC.0)?;
|
||||
rule.SetProfiles(
|
||||
NET_FW_PROFILE2_PRIVATE.0 | NET_FW_PROFILE2_PUBLIC.0 | NET_FW_PROFILE2_DOMAIN.0,
|
||||
)?;
|
||||
rule.SetGrouping(&BSTR::from("EasyTier"))?;
|
||||
|
||||
// 获取规则集合并添加新规则
|
||||
// Get rule collection and add new rule
|
||||
let rules = policy.Rules()?;
|
||||
rules.Remove(&name)?; // 先删除同名规则
|
||||
rules.Remove(&name)?; // Remove existing rule with same name first
|
||||
rules.Add(&rule)?;
|
||||
}
|
||||
|
||||
@@ -225,6 +228,266 @@ pub fn add_self_to_firewall_allowlist() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add firewall rules for specified network interface to allow all traffic
|
||||
pub fn add_interface_to_firewall_allowlist(interface_name: &str) -> anyhow::Result<()> {
|
||||
let _com = ComInitializer::new()?;
|
||||
|
||||
// Create firewall policy instance
|
||||
let policy: INetFwPolicy2 = unsafe {
|
||||
CoCreateInstance(
|
||||
&windows::Win32::NetworkManagement::WindowsFirewall::NetFwPolicy2,
|
||||
None,
|
||||
CLSCTX_ALL,
|
||||
)
|
||||
}?;
|
||||
|
||||
tracing::info!(
|
||||
"Adding comprehensive firewall rules for interface: {}",
|
||||
interface_name
|
||||
);
|
||||
|
||||
// Create rules for each protocol type
|
||||
add_protocol_firewall_rules(&policy, interface_name, "TCP", 6)?; // TCP protocol number 6
|
||||
tracing::debug!("Added TCP firewall rules for interface: {}", interface_name);
|
||||
|
||||
add_protocol_firewall_rules(&policy, interface_name, "UDP", 17)?; // UDP protocol number 17
|
||||
tracing::debug!("Added UDP firewall rules for interface: {}", interface_name);
|
||||
|
||||
add_protocol_firewall_rules(&policy, interface_name, "ICMP", 1)?; // ICMP protocol number 1
|
||||
tracing::debug!(
|
||||
"Added ICMP firewall rules for interface: {}",
|
||||
interface_name
|
||||
);
|
||||
|
||||
// Add fallback rules for all protocols
|
||||
add_all_protocols_firewall_rules(&policy, interface_name)?;
|
||||
tracing::debug!(
|
||||
"Added fallback all-protocols rules for interface: {}",
|
||||
interface_name
|
||||
);
|
||||
|
||||
tracing::info!(
|
||||
"Successfully created all firewall rules for interface: {}",
|
||||
interface_name
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add firewall rules for a specific protocol
|
||||
fn add_protocol_firewall_rules(
|
||||
policy: &INetFwPolicy2,
|
||||
interface_name: &str,
|
||||
protocol_name: &str,
|
||||
protocol_number: i32,
|
||||
) -> anyhow::Result<()> {
|
||||
// Create rules for both inbound and outbound traffic
|
||||
for (is_inbound, direction_name) in [(true, "Inbound"), (false, "Outbound")] {
|
||||
// Create firewall rule instance
|
||||
let rule: INetFwRule = unsafe {
|
||||
CoCreateInstance(
|
||||
&windows::Win32::NetworkManagement::WindowsFirewall::NetFwRule,
|
||||
None,
|
||||
CLSCTX_ALL,
|
||||
)
|
||||
}?;
|
||||
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - {} Protocol ({})",
|
||||
interface_name, protocol_name, direction_name
|
||||
);
|
||||
let description = format!(
|
||||
"Allow {} traffic on EasyTier interface {}",
|
||||
protocol_name, interface_name
|
||||
);
|
||||
|
||||
let name_bstr = BSTR::from(&rule_name);
|
||||
let desc_bstr = BSTR::from(&description);
|
||||
|
||||
unsafe {
|
||||
rule.SetName(&name_bstr)?;
|
||||
rule.SetDescription(&desc_bstr)?;
|
||||
rule.SetProtocol(protocol_number)?;
|
||||
rule.SetAction(NET_FW_ACTION_ALLOW)?;
|
||||
|
||||
if is_inbound {
|
||||
rule.SetDirection(NET_FW_RULE_DIR_IN)?;
|
||||
} else {
|
||||
rule.SetDirection(NET_FW_RULE_DIR_OUT)?;
|
||||
}
|
||||
|
||||
rule.SetEnabled(windows::Win32::Foundation::VARIANT_TRUE)?;
|
||||
rule.SetProfiles(
|
||||
NET_FW_PROFILE2_PRIVATE.0 | NET_FW_PROFILE2_PUBLIC.0 | NET_FW_PROFILE2_DOMAIN.0,
|
||||
)?;
|
||||
rule.SetGrouping(&BSTR::from("EasyTier"))?;
|
||||
|
||||
// Get rule collection and add new rule
|
||||
let rules = policy.Rules()?;
|
||||
rules.Remove(&name_bstr)?; // Remove existing rule with same name first
|
||||
rules.Add(&rule)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add fallback rules for all protocols
|
||||
fn add_all_protocols_firewall_rules(
|
||||
policy: &INetFwPolicy2,
|
||||
interface_name: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
// Create rules for both inbound and outbound traffic
|
||||
for (is_inbound, direction_name) in [(true, "Inbound"), (false, "Outbound")] {
|
||||
// Create firewall rule instance
|
||||
let rule: INetFwRule = unsafe {
|
||||
CoCreateInstance(
|
||||
&windows::Win32::NetworkManagement::WindowsFirewall::NetFwRule,
|
||||
None,
|
||||
CLSCTX_ALL,
|
||||
)
|
||||
}?;
|
||||
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - All Protocols ({})",
|
||||
interface_name, direction_name
|
||||
);
|
||||
let description = format!(
|
||||
"Allow all protocol traffic on EasyTier interface {}",
|
||||
interface_name
|
||||
);
|
||||
|
||||
let name_bstr = BSTR::from(&rule_name);
|
||||
let desc_bstr = BSTR::from(&description);
|
||||
|
||||
unsafe {
|
||||
rule.SetName(&name_bstr)?;
|
||||
rule.SetDescription(&desc_bstr)?;
|
||||
// Don't set protocol - allows all protocols by default
|
||||
rule.SetAction(NET_FW_ACTION_ALLOW)?;
|
||||
|
||||
if is_inbound {
|
||||
rule.SetDirection(NET_FW_RULE_DIR_IN)?;
|
||||
} else {
|
||||
rule.SetDirection(NET_FW_RULE_DIR_OUT)?;
|
||||
}
|
||||
|
||||
rule.SetEnabled(windows::Win32::Foundation::VARIANT_TRUE)?;
|
||||
rule.SetProfiles(
|
||||
NET_FW_PROFILE2_PRIVATE.0 | NET_FW_PROFILE2_PUBLIC.0 | NET_FW_PROFILE2_DOMAIN.0,
|
||||
)?;
|
||||
rule.SetGrouping(&BSTR::from("EasyTier"))?;
|
||||
|
||||
// Get rule collection and add new rule
|
||||
let rules = policy.Rules()?;
|
||||
rules.Remove(&name_bstr)?; // Remove existing rule with same name first
|
||||
rules.Add(&rule)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove firewall rules for specified interface
|
||||
pub fn remove_interface_firewall_rules(interface_name: &str) -> anyhow::Result<()> {
|
||||
let _com = ComInitializer::new()?;
|
||||
|
||||
let policy: INetFwPolicy2 = unsafe {
|
||||
CoCreateInstance(
|
||||
&windows::Win32::NetworkManagement::WindowsFirewall::NetFwPolicy2,
|
||||
None,
|
||||
CLSCTX_ALL,
|
||||
)
|
||||
}?;
|
||||
|
||||
let rules = unsafe { policy.Rules()? };
|
||||
|
||||
// Remove protocol-specific rules
|
||||
for protocol_name in ["TCP", "UDP", "ICMP"] {
|
||||
for direction in ["Inbound", "Outbound"] {
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - {} Protocol ({})",
|
||||
interface_name, protocol_name, direction
|
||||
);
|
||||
let name_bstr = BSTR::from(&rule_name);
|
||||
unsafe {
|
||||
let _ = rules.Remove(&name_bstr); // Ignore errors, rule might not exist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove fallback protocol rules
|
||||
for direction in ["Inbound", "Outbound"] {
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - All Protocols ({})",
|
||||
interface_name, direction
|
||||
);
|
||||
let name_bstr = BSTR::from(&rule_name);
|
||||
unsafe {
|
||||
let _ = rules.Remove(&name_bstr); // Ignore errors, rule might not exist
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List EasyTier firewall rules for specified interface (for debugging)
|
||||
#[allow(dead_code)]
|
||||
pub fn list_interface_firewall_rules(interface_name: &str) -> anyhow::Result<Vec<String>> {
|
||||
let _com = ComInitializer::new()?;
|
||||
|
||||
let policy: INetFwPolicy2 = unsafe {
|
||||
CoCreateInstance(
|
||||
&windows::Win32::NetworkManagement::WindowsFirewall::NetFwPolicy2,
|
||||
None,
|
||||
CLSCTX_ALL,
|
||||
)
|
||||
}?;
|
||||
|
||||
let rules = unsafe { policy.Rules()? };
|
||||
let mut found_rules = Vec::new();
|
||||
|
||||
// Check protocol-specific rules
|
||||
for protocol_name in ["TCP", "UDP", "ICMP"] {
|
||||
for direction in ["Inbound", "Outbound"] {
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - {} Protocol ({})",
|
||||
interface_name, protocol_name, direction
|
||||
);
|
||||
if check_rule_exists(&rules, &rule_name)? {
|
||||
found_rules.push(rule_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check fallback protocol rules
|
||||
for direction in ["Inbound", "Outbound"] {
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - All Protocols ({})",
|
||||
interface_name, direction
|
||||
);
|
||||
if check_rule_exists(&rules, &rule_name)? {
|
||||
found_rules.push(rule_name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(found_rules)
|
||||
}
|
||||
|
||||
/// Check if a firewall rule with specified name exists
|
||||
fn check_rule_exists(
|
||||
rules: &windows::Win32::NetworkManagement::WindowsFirewall::INetFwRules,
|
||||
rule_name: &str,
|
||||
) -> anyhow::Result<bool> {
|
||||
let name_bstr = BSTR::from(rule_name);
|
||||
unsafe {
|
||||
match rules.Item(&name_bstr) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -234,4 +497,71 @@ mod tests {
|
||||
let res = add_self_to_firewall_allowlist();
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // Requires administrator privileges, ignored by default
|
||||
fn test_interface_firewall_rules() {
|
||||
let test_interface = "test_interface";
|
||||
|
||||
// Add firewall rules
|
||||
let add_result = add_interface_to_firewall_allowlist(test_interface);
|
||||
assert!(
|
||||
add_result.is_ok(),
|
||||
"Failed to add interface firewall rules: {:?}",
|
||||
add_result
|
||||
);
|
||||
|
||||
println!(
|
||||
"✓ Added comprehensive firewall rules for interface: {}",
|
||||
test_interface
|
||||
);
|
||||
|
||||
// Verify rules were created
|
||||
let rules = list_interface_firewall_rules(test_interface).unwrap();
|
||||
println!("Created {} firewall rules:", rules.len());
|
||||
for rule in &rules {
|
||||
println!(" - {}", rule);
|
||||
}
|
||||
|
||||
// Verify required protocol rules are all created
|
||||
let expected_protocols = ["TCP", "UDP", "ICMP"];
|
||||
let expected_directions = ["Inbound", "Outbound"];
|
||||
|
||||
for protocol in &expected_protocols {
|
||||
for direction in &expected_directions {
|
||||
let rule_name = format!(
|
||||
"EasyTier {} - {} Protocol ({})",
|
||||
test_interface, protocol, direction
|
||||
);
|
||||
assert!(
|
||||
rules.contains(&rule_name),
|
||||
"Missing required rule: {}",
|
||||
rule_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("✓ All required protocol rules (TCP/UDP/ICMP) are present");
|
||||
|
||||
// Remove firewall rules
|
||||
let remove_result = remove_interface_firewall_rules(test_interface);
|
||||
assert!(
|
||||
remove_result.is_ok(),
|
||||
"Failed to remove interface firewall rules: {:?}",
|
||||
remove_result
|
||||
);
|
||||
|
||||
// Verify rules were removed
|
||||
let remaining_rules = list_interface_firewall_rules(test_interface).unwrap();
|
||||
assert!(
|
||||
remaining_rules.is_empty(),
|
||||
"Some rules were not removed: {:?}",
|
||||
remaining_rules
|
||||
);
|
||||
|
||||
println!(
|
||||
"✓ Successfully removed all firewall rules for interface: {}",
|
||||
test_interface
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +248,20 @@ pub struct VirtualNic {
|
||||
ifcfg: Box<dyn IfConfiguerTrait + Send + Sync + 'static>,
|
||||
}
|
||||
|
||||
impl Drop for VirtualNic {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if let Some(ref ifname) = self.ifname {
|
||||
// Try to clean up firewall rules, but don't panic in destructor
|
||||
if let Err(e) = crate::arch::windows::remove_interface_firewall_rules(ifname) {
|
||||
eprintln!("Warning: Failed to remove firewall rules for interface {}: {}", ifname, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualNic {
|
||||
pub fn new(global_ctx: ArcGlobalCtx) -> Self {
|
||||
Self {
|
||||
@@ -414,6 +428,25 @@ impl VirtualNic {
|
||||
);
|
||||
|
||||
self.ifname = Some(ifname.to_owned());
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Add firewall rules for virtual NIC interface to allow all traffic
|
||||
match crate::arch::windows::add_interface_to_firewall_allowlist(&ifname) {
|
||||
Ok(_) => {
|
||||
tracing::info!("Successfully configured Windows Firewall for interface: {}", ifname);
|
||||
tracing::info!("All protocols (TCP/UDP/ICMP) are now allowed on interface: {}", ifname);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to configure Windows Firewall for {}: {}", ifname, e);
|
||||
println!("⚠ Warning: Failed to configure Windows Firewall for interface {}.", ifname);
|
||||
println!(" This may cause connectivity issues with ping and other network functions.");
|
||||
println!(" Please run as Administrator or manually configure Windows Firewall.");
|
||||
println!(" Alternatively, you can disable Windows Firewall for testing purposes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::new(ft))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user