diff --git a/Cargo.lock b/Cargo.lock index 0c7b490c..d627ea2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2058,8 +2058,10 @@ dependencies = [ "version-compare", "which 7.0.3", "wildmatch", + "winapi", "windows 0.52.0", "windows-service", + "windows-sys 0.52.0", "winreg 0.52.0", "zerocopy", "zip", @@ -4144,7 +4146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -9798,7 +9800,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/EasyTier.code-workspace b/EasyTier.code-workspace index ac3cdf3a..9e4f1acc 100644 --- a/EasyTier.code-workspace +++ b/EasyTier.code-workspace @@ -3,13 +3,29 @@ { "path": "." }, + { + "name": "core", + "path": "easytier" + }, { "name": "gui", "path": "easytier-gui" }, { - "name": "core", - "path": "easytier" + "name": "web", + "path": "easytier-web" + }, + { + "name": "ffi", + "path": "easytier-contrib/easytier-ffi" + }, + { + "name": "magisk", + "path": "easytier-contrib/easytier-magisk" + }, + { + "name": "openharmony", + "path": "easytier-contrib/easytier-ohrs" }, { "name": "vpnservice", @@ -26,5 +42,7 @@ "i18n-ally.sortKeys": true, // Disable the default formatter "prettier.enable": false, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modifications", } } \ No newline at end of file diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index dfbc8173..b355d663 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -246,6 +246,13 @@ windows = { version = "0.52.0", features = [ encoding = "0.2" winreg = "0.52" windows-service = "0.7.0" +windows-sys = { version = "0.52", features = [ + "Win32_NetworkManagement_IpHelper", + "Win32_NetworkManagement_Ndis", + "Win32_Networking_WinSock", + "Win32_Foundation" +]} +winapi = { version = "0.3.9", features = ["impl-default"] } [build-dependencies] tonic-build = "0.12" diff --git a/easytier/src/common/ifcfg/darwin.rs b/easytier/src/common/ifcfg/darwin.rs index cfce7cc6..3c751534 100644 --- a/easytier/src/common/ifcfg/darwin.rs +++ b/easytier/src/common/ifcfg/darwin.rs @@ -1,8 +1,8 @@ use std::net::Ipv4Addr; -use async_trait::async_trait; - use super::{cidr_to_subnet_mask, run_shell_cmd, Error, IfConfiguerTrait}; +use async_trait::async_trait; +use cidr::{Ipv4Inet, Ipv6Inet}; pub struct MacIfConfiger {} #[async_trait] @@ -66,12 +66,17 @@ impl IfConfiguerTrait for MacIfConfiger { .await } - async fn remove_ip(&self, name: &str, ip: Option) -> Result<(), Error> { + async fn remove_ip(&self, name: &str, ip: Option) -> Result<(), Error> { if ip.is_none() { run_shell_cmd(format!("ifconfig {} inet delete", name).as_str()).await } else { run_shell_cmd( - format!("ifconfig {} inet {} delete", name, ip.unwrap().to_string()).as_str(), + format!( + "ifconfig {} inet {} delete", + name, + ip.unwrap().address().to_string() + ) + .as_str(), ) .await } @@ -87,15 +92,13 @@ impl IfConfiguerTrait for MacIfConfiger { address: std::net::Ipv6Addr, cidr_prefix: u8, ) -> Result<(), Error> { - run_shell_cmd( - format!("ifconfig {} inet6 {}/{} add", name, address, cidr_prefix).as_str(), - ) - .await + run_shell_cmd(format!("ifconfig {} inet6 {}/{} add", name, address, cidr_prefix).as_str()) + .await } - async fn remove_ipv6(&self, name: &str, ip: Option) -> Result<(), Error> { + async fn remove_ipv6(&self, name: &str, ip: Option) -> Result<(), Error> { if let Some(ip) = ip { - run_shell_cmd(format!("ifconfig {} inet6 {} delete", name, ip).as_str()).await + run_shell_cmd(format!("ifconfig {} inet6 {} delete", name, ip.address()).as_str()).await } else { // Remove all IPv6 addresses is more complex on macOS, just succeed Ok(()) diff --git a/easytier/src/common/ifcfg/mod.rs b/easytier/src/common/ifcfg/mod.rs index a64f0a68..37bc3645 100644 --- a/easytier/src/common/ifcfg/mod.rs +++ b/easytier/src/common/ifcfg/mod.rs @@ -3,6 +3,8 @@ mod darwin; #[cfg(any(target_os = "linux"))] mod netlink; #[cfg(target_os = "windows")] +mod win; +#[cfg(target_os = "windows")] mod windows; mod route; @@ -10,6 +12,7 @@ mod route; use std::net::{Ipv4Addr, Ipv6Addr}; use async_trait::async_trait; +use cidr::{Ipv4Inet, Ipv6Inet}; use tokio::process::Command; use super::error::Error; @@ -69,10 +72,10 @@ pub trait IfConfiguerTrait: Send + Sync { async fn set_link_status(&self, _name: &str, _up: bool) -> Result<(), Error> { Ok(()) } - async fn remove_ip(&self, _name: &str, _ip: Option) -> Result<(), Error> { + async fn remove_ip(&self, _name: &str, _ip: Option) -> Result<(), Error> { Ok(()) } - async fn remove_ipv6(&self, _name: &str, _ip: Option) -> Result<(), Error> { + async fn remove_ipv6(&self, _name: &str, _ip: Option) -> Result<(), Error> { Ok(()) } async fn wait_interface_show(&self, _name: &str) -> Result<(), Error> { diff --git a/easytier/src/common/ifcfg/netlink.rs b/easytier/src/common/ifcfg/netlink.rs index 7b2c4e64..4af72341 100644 --- a/easytier/src/common/ifcfg/netlink.rs +++ b/easytier/src/common/ifcfg/netlink.rs @@ -8,7 +8,7 @@ use std::{ use anyhow::Context; use async_trait::async_trait; -use cidr::IpInet; +use cidr::{IpInet, Ipv4Inet, Ipv6Inet}; use netlink_packet_core::{ NetlinkDeserializable, NetlinkHeader, NetlinkMessage, NetlinkPayload, NetlinkSerializable, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, NLM_F_REQUEST, @@ -473,7 +473,7 @@ impl IfConfiguerTrait for NetlinkIfConfiger { Ok(()) } - async fn remove_ip(&self, name: &str, ip: Option) -> Result<(), Error> { + async fn remove_ip(&self, name: &str, ip: Option) -> Result<(), Error> { if ip.is_none() { let addrs = Self::list_addresses(name)?; for addr in addrs { @@ -483,8 +483,8 @@ impl IfConfiguerTrait for NetlinkIfConfiger { } } else { let ip = ip.unwrap(); - let prefix_len = Self::get_prefix_len(name, ip)?; - Self::remove_one_ip(name, ip, prefix_len)?; + let prefix_len = Self::get_prefix_len(name, ip.address())?; + Self::remove_one_ip(name, ip.address(), prefix_len)?; } Ok(()) @@ -519,7 +519,7 @@ impl IfConfiguerTrait for NetlinkIfConfiger { ) } - async fn remove_ipv6(&self, name: &str, ip: Option) -> Result<(), Error> { + async fn remove_ipv6(&self, name: &str, ip: Option) -> Result<(), Error> { if ip.is_none() { let addrs = Self::list_addresses(name)?; for addr in addrs { @@ -530,8 +530,8 @@ impl IfConfiguerTrait for NetlinkIfConfiger { } } else { let ipv6 = ip.unwrap(); - let prefix_len = Self::get_prefix_len_ipv6(name, ipv6)?; - Self::remove_one_ipv6(name, ipv6, prefix_len)?; + let prefix_len = Self::get_prefix_len_ipv6(name, ipv6.address())?; + Self::remove_one_ipv6(name, ipv6.address(), prefix_len)?; } Ok(()) diff --git a/easytier/src/common/ifcfg/win/luid.rs b/easytier/src/common/ifcfg/win/luid.rs new file mode 100644 index 00000000..0e0c2e71 --- /dev/null +++ b/easytier/src/common/ifcfg/win/luid.rs @@ -0,0 +1,745 @@ +// +// Port supporting code from wireguard-windows, as used by 3rd-party/wireguard-go, to Rust +// This file implements functionality similar to: wireguard-windows/tunnel/winipcfg/luid.go +// +// ATTENTION: NOT included are DNS() and SetDNS() - functions to query and set DNS servers for a network interface. +// + +use super::netsh; +use super::types::*; +use cidr::Ipv4Inet; +use cidr::Ipv6Inet; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::ptr; +use winapi::shared::{ + guiddef::GUID, ifdef::NET_LUID, netioapi::*, nldef::*, winerror::*, ws2def::*, ws2ipdef::*, +}; + +pub struct InterfaceLuid { + luid: NET_LUID, +} + +impl InterfaceLuid { + pub fn new(luid_value: u64) -> Self { + InterfaceLuid { + luid: winapi::shared::ifdef::NET_LUID_LH { Value: luid_value }, + } + } + + pub fn luid(&self) -> NET_LUID { + self.luid + } + + /// get_ip_interface method retrieves IP information for the specified interface on the local computer. + pub fn get_ip_interface( + &self, + family: ADDRESS_FAMILY, + ) -> Result { + let mut row = MIB_IPINTERFACE_ROW::default(); + unsafe { InitializeIpInterfaceEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.Family = family; + + let result = unsafe { GetIpInterfaceEntry(&mut row) }; + if NO_ERROR == result { + Ok(row) + } else { + Err(result) + } + } + + /// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-setipinterfaceentry + /// If only InterfaceIndex was specified, SetIpInterfaceEntry() will modify ipif with a correct InterfaceLuid + pub fn set_ip_interface(&self, ipif: *mut MIB_IPINTERFACE_ROW) -> Result<(), NETIO_STATUS> { + let result = unsafe { SetIpInterfaceEntry(ipif) }; + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// get_interface method retrieves information for the specified adapter on the local computer. + /// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getifentry2 + pub fn get_interface(&self) -> Result { + let mut row = MIB_IF_ROW2 { + InterfaceLuid: self.luid, + ..MIB_IF_ROW2::default() + }; + + let result = unsafe { GetIfEntry2(&mut row) }; + if NO_ERROR == result { + Ok(row) + } else { + Err(result) + } + } + + /// GUID method converts a locally unique identifier (LUID) for a network interface to a globally unique identifier (GUID) for the interface. + /// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceluidtoguid + pub fn get_guid(&self) -> Result { + let mut interface_guid = GUID::default(); + + let result = unsafe { ConvertInterfaceLuidToGuid(&self.luid, &mut interface_guid) }; + + if NO_ERROR == result { + Ok(interface_guid) + } else { + Err(result) + } + } + + /// luid_from_guid function converts a globally unique identifier (GUID) for a network interface to the locally unique identifier (LUID) for the interface. + /// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceguidtoluid + pub fn luid_from_guid(interface_guid: &GUID) -> Result { + let mut interface_luid = NET_LUID::default(); + + let result = unsafe { ConvertInterfaceGuidToLuid(interface_guid, &mut interface_luid) }; + + if NO_ERROR == result { + Ok(Self { + luid: interface_luid, + }) + } else { + Err(result) + } + } + + /// luid_from_index function converts a local index for a network interface to the locally unique identifier (LUID) for the interface. + /// https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceindextoluid + pub fn luid_from_index(interface_index: u32) -> Result { + let mut interface_luid = NET_LUID::default(); + + let result = unsafe { ConvertInterfaceIndexToLuid(interface_index, &mut interface_luid) }; + + if NO_ERROR == result { + Ok(Self { + luid: interface_luid, + }) + } else { + Err(result) + } + } + + /// get_from_ipv4_address method returns MibUnicastIPAddressRow struct that matches to provided 'ip' argument. Corresponds to GetUnicastIpAddressEntry + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getunicastipaddressentry) + pub fn get_from_ipv4_address( + &self, + ip: &Ipv4Addr, + ) -> Result { + let mut row = MIB_UNICASTIPADDRESS_ROW::default(); + unsafe { InitializeUnicastIpAddressEntry(&mut row) }; + + unsafe { *row.Address.Ipv4_mut() = convert_ipv4addr_to_sockaddr(ip) }; + + let result = unsafe { GetUnicastIpAddressEntry(&mut row) }; + + if NO_ERROR == result { + Ok(row) + } else { + Err(result) + } + } + + /// get_from_ipv6_address method returns MibUnicastIPAddressRow struct that matches to provided 'ip' argument. Corresponds to GetUnicastIpAddressEntry + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getunicastipaddressentry) + pub fn get_from_ipv6_address( + &self, + ip: &Ipv6Addr, + ) -> Result { + let mut row = MIB_UNICASTIPADDRESS_ROW::default(); + unsafe { InitializeUnicastIpAddressEntry(&mut row) }; + + unsafe { *row.Address.Ipv6_mut() = convert_ipv6addr_to_sockaddr(ip) }; + + let result = unsafe { GetUnicastIpAddressEntry(&mut row) }; + + if NO_ERROR == result { + Ok(row) + } else { + Err(result) + } + } + + /// add_ipv4_address method adds new unicast IP address to the interface. Corresponds to CreateUnicastIpAddressEntry function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry). + pub fn add_ipv4_address(&self, address: &Ipv4Inet) -> Result<(), NETIO_STATUS> { + let mut row = MIB_UNICASTIPADDRESS_ROW::default(); + unsafe { InitializeUnicastIpAddressEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.DadState = IpDadStatePreferred; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + unsafe { *row.Address.Ipv4_mut() = convert_ipv4addr_to_sockaddr(&address.address()) }; + row.OnLinkPrefixLength = address.network_length(); + + let result = unsafe { CreateUnicastIpAddressEntry(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// add_ipv6_address method adds new unicast IP address to the interface. Corresponds to CreateUnicastIpAddressEntry function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry). + pub fn add_ipv6_address(&self, address: &Ipv6Inet) -> Result<(), NETIO_STATUS> { + let mut row = MIB_UNICASTIPADDRESS_ROW::default(); + unsafe { InitializeUnicastIpAddressEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.DadState = IpDadStatePreferred; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + unsafe { *row.Address.Ipv6_mut() = convert_ipv6addr_to_sockaddr(&address.address()) }; + row.OnLinkPrefixLength = address.network_length(); + + let result = unsafe { CreateUnicastIpAddressEntry(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// add_ipv4_addresses method adds multiple new unicast IP addresses to the interface. Corresponds to CreateUnicastIpAddressEntry function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry). + pub fn add_ipv4_addresses( + &self, + addresses: impl IntoIterator, + ) -> Result<(), NETIO_STATUS> { + for ip in addresses.into_iter().enumerate() { + self.add_ipv4_address(&ip.1)?; + } + Ok(()) + } + + /// add_ipv6_addresses method adds multiple new unicast IP addresses to the interface. Corresponds to CreateUnicastIpAddressEntry function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry). + pub fn add_ipv6_addresses( + &self, + addresses: impl IntoIterator, + ) -> Result<(), NETIO_STATUS> { + for ip in addresses.into_iter().enumerate() { + self.add_ipv6_address(&ip.1)?; + } + Ok(()) + } + + /// set_ipv4_addresses method sets new unicast IP addresses to the interface. + pub fn set_ipv4_addresses( + &self, + addresses: impl IntoIterator, + ) -> Result<(), NETIO_STATUS> { + self.flush_ipv4_addresses()?; + self.add_ipv4_addresses(addresses)?; + Ok(()) + } + + /// set_ipv6_addresses method sets new unicast IP addresses to the interface. + pub fn set_ipv6_addresses( + &self, + addresses: impl IntoIterator, + ) -> Result<(), NETIO_STATUS> { + self.flush_ipv6_addresses()?; + self.add_ipv6_addresses(addresses)?; + Ok(()) + } + + /// delete_ipv4_address method deletes interface's unicast IP address. Corresponds to DeleteUnicastIpAddressEntry function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteunicastipaddressentry). + pub fn delete_ipv4_address(&self, address: &Ipv4Inet) -> Result<(), NETIO_STATUS> { + let mut row = MIB_UNICASTIPADDRESS_ROW::default(); + unsafe { InitializeUnicastIpAddressEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.DadState = IpDadStatePreferred; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + unsafe { *row.Address.Ipv4_mut() = convert_ipv4addr_to_sockaddr(&address.address()) }; + row.OnLinkPrefixLength = address.network_length(); + + let result = unsafe { DeleteUnicastIpAddressEntry(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// delete_ipv4_address method deletes interface's unicast IP address. Corresponds to DeleteUnicastIpAddressEntry function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteunicastipaddressentry). + pub fn delete_ipv4_address2( + &self, + address: *const SOCKADDR_IN, + prefix_len: u8, + ) -> Result<(), NETIO_STATUS> { + let mut row = MIB_UNICASTIPADDRESS_ROW::default(); + unsafe { InitializeUnicastIpAddressEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.DadState = IpDadStatePreferred; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + assert!(!address.is_null()); + unsafe { *row.Address.Ipv4_mut() = *address }; + row.OnLinkPrefixLength = prefix_len; + + let result = unsafe { DeleteUnicastIpAddressEntry(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// delete_ipv6_address method deletes interface's unicast IP address. Corresponds to DeleteUnicastIpAddressEntry function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteunicastipaddressentry). + pub fn delete_ipv6_address(&self, address: &Ipv6Inet) -> Result<(), NETIO_STATUS> { + let mut row = MIB_UNICASTIPADDRESS_ROW::default(); + unsafe { InitializeUnicastIpAddressEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.DadState = IpDadStatePreferred; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + unsafe { *row.Address.Ipv6_mut() = convert_ipv6addr_to_sockaddr(&address.address()) }; + row.OnLinkPrefixLength = address.network_length(); + + let result = unsafe { DeleteUnicastIpAddressEntry(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// delete_ipv6_address method deletes interface's unicast IP address. Corresponds to DeleteUnicastIpAddressEntry function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteunicastipaddressentry). + pub fn delete_ipv6_address2( + &self, + address: *const SOCKADDR_IN6, + prefix_len: u8, + ) -> Result<(), NETIO_STATUS> { + let mut row = MIB_UNICASTIPADDRESS_ROW::default(); + unsafe { InitializeUnicastIpAddressEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.DadState = IpDadStatePreferred; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + assert!(!address.is_null()); + unsafe { *row.Address.Ipv6_mut() = *address }; + row.OnLinkPrefixLength = prefix_len; + + let result = unsafe { DeleteUnicastIpAddressEntry(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// flush_ip_addresses method deletes all interface's unicast IP addresses. + pub fn flush_ip_addresses(&self, address_family: ADDRESS_FAMILY) -> Result<(), NETIO_STATUS> { + let mut p_table: PMIB_UNICASTIPADDRESS_TABLE = ptr::null_mut(); + let result = unsafe { GetUnicastIpAddressTable(address_family, &mut p_table) }; + if NO_ERROR != result { + return Err(result); + } + + assert!(!p_table.is_null()); + let num_entries = unsafe { *p_table }.NumEntries; + let x_table = unsafe { *p_table }.Table.as_ptr(); + for i in 0..num_entries { + let current_entry = unsafe { x_table.add(i as _) }; + if unsafe { (*current_entry).InterfaceLuid.Value } == self.luid.Value { + unsafe { DeleteUnicastIpAddressEntry(current_entry) }; + } + } + + unsafe { FreeMibTable(p_table as _) }; + + Ok(()) + } + + /// flush_ipv4_addresses method deletes all interface's unicast IP addresses. + pub fn flush_ipv4_addresses(&self) -> Result<(), NETIO_STATUS> { + self.flush_ip_addresses(AF_INET as _) + } + + /// flush_ipv6_addresses method deletes all interface's unicast IP addresses. + pub fn flush_ipv6_addresses(&self) -> Result<(), NETIO_STATUS> { + self.flush_ip_addresses(AF_INET6 as _) + } + + /// route_ipv4 method returns route determined with the input arguments. Corresponds to GetIpForwardEntry2 function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipforwardentry2). + /// NOTE: If the corresponding route isn't found, the method will return error. + pub fn route_ipv4( + &self, + destination: &Ipv4Inet, + next_hop: &Ipv4Addr, + ) -> Result { + let mut row = MIB_IPFORWARD_ROW2::default(); + unsafe { InitializeIpForwardEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + unsafe { + *row.DestinationPrefix.Prefix.Ipv4_mut() = + convert_ipv4addr_to_sockaddr(&destination.address()) + }; + row.DestinationPrefix.PrefixLength = destination.network_length(); + + unsafe { *row.NextHop.Ipv4_mut() = convert_ipv4addr_to_sockaddr(next_hop) }; + + let result = unsafe { GetIpForwardEntry2(&mut row) }; + + if NO_ERROR == result { + Ok(row) + } else { + Err(result) + } + } + + /// route_ipv6 method returns route determined with the input arguments. Corresponds to GetIpForwardEntry2 function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipforwardentry2). + /// NOTE: If the corresponding route isn't found, the method will return error. + pub fn route_ipv6( + &self, + destination: &Ipv6Inet, + next_hop: &Ipv6Addr, + ) -> Result { + let mut row = MIB_IPFORWARD_ROW2::default(); + unsafe { InitializeIpForwardEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + unsafe { + *row.DestinationPrefix.Prefix.Ipv6_mut() = + convert_ipv6addr_to_sockaddr(&destination.address()) + }; + row.DestinationPrefix.PrefixLength = destination.network_length(); + + unsafe { *row.NextHop.Ipv6_mut() = convert_ipv6addr_to_sockaddr(next_hop) }; + + let result = unsafe { GetIpForwardEntry2(&mut row) }; + + if NO_ERROR == result { + Ok(row) + } else { + Err(result) + } + } + + /// add_route_ipv4 method adds a route to the interface. Corresponds to CreateIpForwardEntry2 function, with added splitDefault feature. + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createipforwardentry2) + pub fn add_route_ipv4( + &self, + destination: &Ipv4Inet, + next_hop: &Ipv4Addr, + metric: u32, + ) -> Result<(), NETIO_STATUS> { + let mut row = MIB_IPFORWARD_ROW2::default(); + unsafe { InitializeIpForwardEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + unsafe { + *row.DestinationPrefix.Prefix.Ipv4_mut() = + convert_ipv4addr_to_sockaddr(&destination.address()) + }; + row.DestinationPrefix.PrefixLength = destination.network_length(); + + unsafe { *row.NextHop.Ipv4_mut() = convert_ipv4addr_to_sockaddr(next_hop) }; + + row.Metric = metric; + + let result = unsafe { CreateIpForwardEntry2(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// add_route_ipv6 method adds a route to the interface. Corresponds to CreateIpForwardEntry2 function, with added splitDefault feature. + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createipforwardentry2) + pub fn add_route_ipv6( + &self, + destination: &Ipv6Inet, + next_hop: &Ipv6Addr, + metric: u32, + ) -> Result<(), NETIO_STATUS> { + let mut row = MIB_IPFORWARD_ROW2::default(); + unsafe { InitializeIpForwardEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + + unsafe { + *row.DestinationPrefix.Prefix.Ipv6_mut() = + convert_ipv6addr_to_sockaddr(&destination.address()) + }; + row.DestinationPrefix.PrefixLength = destination.network_length(); + + unsafe { *row.NextHop.Ipv6_mut() = convert_ipv6addr_to_sockaddr(next_hop) }; + + row.Metric = metric; + + let result = unsafe { CreateIpForwardEntry2(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// add_routes_ipv4 method adds multiple routes to the interface + pub fn add_routes_ipv4( + &self, + routes_data: impl IntoIterator, + ) -> Result<(), NETIO_STATUS> { + for rd in routes_data.into_iter().enumerate() { + self.add_route_ipv4(&rd.1.destination, &rd.1.next_hop, rd.1.metric)?; + } + Ok(()) + } + + /// add_routes_ipv6 method adds multiple routes to the interface + pub fn add_routes_ipv6( + &self, + routes_data: impl IntoIterator, + ) -> Result<(), NETIO_STATUS> { + for rd in routes_data.into_iter().enumerate() { + self.add_route_ipv6(&rd.1.destination, &rd.1.next_hop, rd.1.metric)?; + } + Ok(()) + } + + /// set_routes_ipv4 method sets (flush than add) multiple routes to the interface. + pub fn set_routes_ipv4( + &self, + routes_data: impl IntoIterator, + ) -> Result<(), NETIO_STATUS> { + self.flush_routes_ipv4()?; + self.add_routes_ipv4(routes_data)?; + Ok(()) + } + + /// set_routes_ipv6 method sets (flush than add) multiple routes to the interface. + pub fn set_routes_ipv6( + &self, + routes_data: impl IntoIterator, + ) -> Result<(), NETIO_STATUS> { + self.flush_routes_ipv6()?; + self.add_routes_ipv6(routes_data)?; + Ok(()) + } + + /// delete_route_ipv4 method deletes a route that matches the criteria. Corresponds to DeleteIpForwardEntry2 function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteipforwardentry2). + pub fn delete_route_ipv4( + &self, + destination: &Ipv4Inet, + next_hop: &Ipv4Addr, + ) -> Result<(), NETIO_STATUS> { + let mut row = MIB_IPFORWARD_ROW2::default(); + unsafe { InitializeIpForwardEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + + unsafe { + *row.DestinationPrefix.Prefix.Ipv4_mut() = + convert_ipv4addr_to_sockaddr(&destination.address()) + }; + row.DestinationPrefix.PrefixLength = destination.network_length(); + + unsafe { *row.NextHop.Ipv4_mut() = convert_ipv4addr_to_sockaddr(next_hop) }; + + let result = unsafe { GetIpForwardEntry2(&mut row) }; + if NO_ERROR != result { + return Err(result); + } + + let result = unsafe { DeleteIpForwardEntry2(&row) }; + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// delete_route_ipv6 method deletes a route that matches the criteria. Corresponds to DeleteIpForwardEntry2 function + /// (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteipforwardentry2). + pub fn delete_route_ipv6( + &self, + destination: &Ipv6Inet, + next_hop: &Ipv6Addr, + ) -> Result<(), NETIO_STATUS> { + let mut row = MIB_IPFORWARD_ROW2::default(); + unsafe { InitializeIpForwardEntry(&mut row) }; + + row.InterfaceLuid = self.luid; + + unsafe { + *row.DestinationPrefix.Prefix.Ipv6_mut() = + convert_ipv6addr_to_sockaddr(&destination.address()) + }; + row.DestinationPrefix.PrefixLength = destination.network_length(); + + unsafe { *row.NextHop.Ipv6_mut() = convert_ipv6addr_to_sockaddr(next_hop) }; + + let result = unsafe { GetIpForwardEntry2(&mut row) }; + if NO_ERROR != result { + return Err(result); + } + + let result = unsafe { DeleteIpForwardEntry2(&row) }; + + if NO_ERROR == result { + Ok(()) + } else { + Err(result) + } + } + + /// flush_routes method deletes all interface's routes. + /// It continues on failures, and returns the last error afterwards. + pub fn flush_routes(&self, address_family: ADDRESS_FAMILY) -> Result<(), NETIO_STATUS> { + let mut last_error: NETIO_STATUS = NO_ERROR; + + let mut p_table: PMIB_IPFORWARD_TABLE2 = ptr::null_mut(); + let result = unsafe { GetIpForwardTable2(address_family, &mut p_table) }; + if NO_ERROR != result { + return Err(result); + } + + assert!(!p_table.is_null()); + let num_entries = unsafe { *p_table }.NumEntries; + let x_table = unsafe { *p_table }.Table.as_ptr(); + for i in 0..num_entries { + let current_entry = unsafe { x_table.add(i as _) }; + if unsafe { (*current_entry).InterfaceLuid.Value } == self.luid.Value { + let result = unsafe { DeleteIpForwardEntry2(current_entry) }; + if NO_ERROR != result { + last_error = result; + } + } + } + + unsafe { FreeMibTable(p_table as _) }; + + if NO_ERROR == last_error { + Ok(()) + } else { + Err(result) + } + } + + /// flush_routes_ipv4 method deletes all interface's routes. + /// It continues on failures, and returns the last error afterwards. + pub fn flush_routes_ipv4(&self) -> Result<(), NETIO_STATUS> { + self.flush_routes(AF_INET as _) + } + + /// flush_routes_ipv6 method deletes all interface's routes. + /// It continues on failures, and returns the last error afterwards. + pub fn flush_routes_ipv6(&self) -> Result<(), NETIO_STATUS> { + self.flush_routes(AF_INET6 as _) + } + + /// flush_dns method clears all DNS servers associated with the adapter. + fn flush_dns(&self, family: ADDRESS_FAMILY) -> Result<(), String> { + let ip_itf = match self.get_ip_interface(family) { + Ok(ip_itf) => ip_itf, + Err(_) => { + return Err(String::from("Failed to obtain interface")); + } + }; + + netsh::flush_dns(family, ip_itf.InterfaceIndex) + } + + /// flush_dns_ipv4 method clears all DNS servers associated with the adapter. + pub fn flush_dns_ipv4(&self) -> Result<(), String> { + self.flush_dns(AF_INET as _) + } + + /// flush_dns_ipv6 method clears all DNS servers associated with the adapter. + pub fn flush_dns_ipv6(&self) -> Result<(), String> { + self.flush_dns(AF_INET6 as _) + } + + /// Sets MTU on the interface + /// TODO: Set IP and other things in here too, so the code is more organized + pub fn set_iface_config(&self, mtu: u32) -> Result<(), NETIO_STATUS> { + // SAFETY: Both NET_LUID_LH unions should be the same. We're just copying out + // the u64 value and re-wrapping it, since wintun doesn't refer to the windows + // crate's version of NET_LUID_LH. + self.try_set_mtu(AF_INET as ADDRESS_FAMILY, mtu)?; + self.try_set_mtu(AF_INET6 as ADDRESS_FAMILY, mtu)?; + Ok(()) + } + + fn try_set_mtu(&self, family: ADDRESS_FAMILY, mut mtu: u32) -> Result<(), NETIO_STATUS> { + let mut row = MIB_IPINTERFACE_ROW { + Family: family, + InterfaceLuid: self.luid, + ..Default::default() + }; + + // SAFETY: TODO + let error = unsafe { GetIpInterfaceEntry(&mut row) }; + if error != NO_ERROR { + if family == (AF_INET6 as ADDRESS_FAMILY) && error == ERROR_NOT_FOUND { + tracing::debug!(?family, "Couldn't set MTU, maybe IPv6 is disabled."); + } else { + tracing::warn!(?family, "Couldn't set MTU: {}", error); + } + return Err(error); + } + + if family == (AF_INET6 as ADDRESS_FAMILY) { + // ipv6 mtu must be at least 1280 + mtu = 1280.max(mtu); + } + + // https://stackoverflow.com/questions/54857292/setipinterfaceentry-returns-error-invalid-parameter + row.SitePrefixLength = 0; + + row.NlMtu = mtu; + + // SAFETY: TODO + let ret = unsafe { SetIpInterfaceEntry(&mut row) }; + if NO_ERROR == ret { + Ok(()) + } else { + Err(ret) + } + } +} diff --git a/easytier/src/common/ifcfg/win/mod.rs b/easytier/src/common/ifcfg/win/mod.rs new file mode 100644 index 00000000..6e140a5f --- /dev/null +++ b/easytier/src/common/ifcfg/win/mod.rs @@ -0,0 +1,3 @@ +pub mod netsh; +pub mod types; +pub mod luid; \ No newline at end of file diff --git a/easytier/src/common/ifcfg/win/netsh.rs b/easytier/src/common/ifcfg/win/netsh.rs new file mode 100644 index 00000000..dcf368c9 --- /dev/null +++ b/easytier/src/common/ifcfg/win/netsh.rs @@ -0,0 +1,118 @@ +// +// Port supporting code for wireguard-nt from wireguard-windows v0.5.3 to Rust +// This file replicates the functionality of wireguard-windows/tunnel/winipcfg/netsh.go +// + +use std::{ + net::{Ipv4Addr, Ipv6Addr}, + process::{Command, Stdio}, +}; +use winapi::shared::ws2def::{ADDRESS_FAMILY, AF_INET, AF_INET6}; + +pub fn flush_dns(family: ADDRESS_FAMILY, if_index: u32) -> Result<(), String> { + let proto_name = match family as i32 { + AF_INET => "ipv4", + AF_INET6 => "ipv6", + _ => { + return Err(String::from("Invalid address family")); + } + }; + + //let netsh_params = format!("interface {proto} set dnsservers name={itf} source=static address=none validate=no register=both", proto=proto_name, itf=ip_itf.InterfaceIndex); + let ret_netsh = Command::new("netsh.exe") + .arg("interface") + .arg(proto_name) + .arg("set") + .arg("dnsservers") + .arg(format!("name={}", if_index)) + .arg("source=static") + .arg("address=none") + .arg("validate=no") + .arg("register=both") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .output(); + match ret_netsh { + Ok(output) => { + // netsh.exe returns error messages only and is silent upon success. BUT then it will return \r\n, so we need to look at the lines. + if let Ok(stdout_str) = String::from_utf8(output.stdout) { + if stdout_str.is_empty() || stdout_str == "\r\n" { + Ok(()) + } else { + // TODO: ignore "There are no Domain Name Servers (DNS) configured on this computer." + // Is this string localized? + Err(stdout_str) + } + } else { + Err(String::from("Could not parse netsh output")) + } + } + Err(_) => Err(String::from("Failed to execute command")), + } +} + +// Please execute flush_dns() first, as written in the original source code. +fn add_dns(family: ADDRESS_FAMILY, if_index: u32, dnses: &[String]) -> Result<(), String> { + let proto_name = match family as i32 { + AF_INET => "ipv4", + AF_INET6 => "ipv6", + _ => { + return Err(String::from("Invalid address family")); + } + }; + + // "interface ipv4 add dnsservers name=%d address=%s validate=no" + let ret_netsh = Command::new("netsh.exe") + .arg("interface") + .arg(proto_name) + .arg("add") + .arg("dnsservers") + .arg(format!("name={}", if_index)) + .arg(format!( + "address={}", + dnses + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(",") + )) + .arg("validate=no") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .output(); + match ret_netsh { + Ok(output) => { + // netsh.exe returns error messages only and is silent upon success. BUT then it will return \r\n, so we need to look at the lines. + if let Ok(stdout_str) = String::from_utf8(output.stdout) { + if stdout_str.is_empty() || stdout_str == "\r\n" { + Ok(()) + } else { + // TODO: ignore "There are no Domain Name Servers (DNS) configured on this computer." + // Is this string localized? + Err(stdout_str) + } + } else { + Err(String::from("Could not parse netsh output")) + } + } + Err(_) => Err(String::from("Failed to execute command")), + } +} + +pub fn add_dns_ipv4(if_index: u32, dnses: &[Ipv4Addr]) -> Result<(), String> { + flush_dns(AF_INET as _, if_index)?; + if dnses.is_empty() { + return Ok(()); + } + let dnses_str: Vec = dnses.iter().map(|addr| addr.to_string()).collect(); + add_dns(AF_INET as _, if_index, &dnses_str) +} + +pub fn add_dns_ipv6(if_index: u32, dnses: &[Ipv6Addr]) -> Result<(), String> { + flush_dns(AF_INET6 as _, if_index)?; + if dnses.is_empty() { + return Ok(()); + } + let dnses_str: Vec = dnses.iter().map(|addr| addr.to_string()).collect(); + add_dns(AF_INET6 as _, if_index, &dnses_str) +} \ No newline at end of file diff --git a/easytier/src/common/ifcfg/win/types.rs b/easytier/src/common/ifcfg/win/types.rs new file mode 100644 index 00000000..c0d90e6a --- /dev/null +++ b/easytier/src/common/ifcfg/win/types.rs @@ -0,0 +1,103 @@ +// +// Port supporting code for wireguard-nt from wireguard-windows v0.5.3 to Rust +// This file replicates parts of wireguard-windows/tunnel/winipcfg/types.go +// + +use cidr::{Ipv4Inet, Ipv6Inet}; +use std::ffi::OsString; +use std::net::{Ipv4Addr, Ipv6Addr}; +use std::os::windows::prelude::*; +use winapi::shared::ws2def::*; +use winapi::shared::ws2ipdef::*; + +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct RouteDataIpv4 { + pub destination: Ipv4Inet, + pub next_hop: Ipv4Addr, + pub metric: u32, +} + +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct RouteDataIpv6 { + pub destination: Ipv6Inet, + pub next_hop: Ipv6Addr, + pub metric: u32, +} + +/// This function converts std::net::Ipv4Addr to winapi::shared::inaddr::in_addr +#[inline] +pub fn convert_ipv4addr_to_inaddr(ip: &Ipv4Addr) -> winapi::shared::inaddr::in_addr { + let mut winaddr = winapi::shared::inaddr::in_addr::default(); + + let s_un_b = unsafe { winaddr.S_un.S_un_b_mut() }; + s_un_b.s_b1 = ip.octets()[0]; + s_un_b.s_b2 = ip.octets()[1]; + s_un_b.s_b3 = ip.octets()[2]; + s_un_b.s_b4 = ip.octets()[3]; + + winaddr +} + +/// This function converts std::net::Ipv6Addr to winapi::shared::in6addr::in6_addr +#[inline] +pub fn convert_ipv6addr_to_inaddr(ip: &Ipv6Addr) -> winapi::shared::in6addr::in6_addr { + let mut winaddr = winapi::shared::in6addr::in6_addr::default(); + let octets = ip.octets(); + for i in 0..octets.len() { + unsafe { winaddr.u.Byte_mut()[i] = octets[i] }; + } + + winaddr +} + +/// This function converts std::net::Ipv4Addr to winapi::shared::ws2def::SOCKADDR_IN +pub fn convert_ipv4addr_to_sockaddr(ip: &Ipv4Addr) -> SOCKADDR_IN { + SOCKADDR_IN { + sin_family: AF_INET as ADDRESS_FAMILY, + sin_addr: convert_ipv4addr_to_inaddr(ip), + ..Default::default() + } +} + +/// This function converts ipnet::Ipv6Addr to winapi::shared::ws2ipdef::SOCKADDR_IN6 +pub fn convert_ipv6addr_to_sockaddr(ip: &Ipv6Addr) -> SOCKADDR_IN6 { + SOCKADDR_IN6 { + sin6_family: AF_INET6 as ADDRESS_FAMILY, + sin6_addr: convert_ipv6addr_to_inaddr(ip), + ..Default::default() + } +} + +/// This function converts winapi::shared::ws2def::SOCKADDR_IN to std::net::Ipv4Addr +pub fn convert_sockaddr_to_ipv4addr(sockaddr: &SOCKADDR_IN) -> Ipv4Addr { + unsafe { + Ipv4Addr::new( + sockaddr.sin_addr.S_un.S_un_b().s_b1, + sockaddr.sin_addr.S_un.S_un_b().s_b2, + sockaddr.sin_addr.S_un.S_un_b().s_b3, + sockaddr.sin_addr.S_un.S_un_b().s_b4, + ) + } +} + +/// This function converts a null-terminated Windows Unicode PWCHAR/LPWSTR to an OsString +pub fn u16_ptr_to_osstring(ptr: *const u16) -> OsString { + assert!(!ptr.is_null()); + let len = (0..) + .take_while(|&i| unsafe { *ptr.offset(i) } != 0) + .count(); + let slice = unsafe { std::slice::from_raw_parts(ptr, len) }; + + OsString::from_wide(slice) +} + +/// This function converts a null-terminated Windows PWCHAR/LPWSTR to a String +pub fn u16_ptr_to_string(ptr: *const u16) -> String { + assert!(!ptr.is_null()); + let len = (0..) + .take_while(|&i| unsafe { *ptr.offset(i) } != 0) + .count(); + let slice = unsafe { std::slice::from_raw_parts(ptr, len) }; + + String::from_utf16_lossy(slice) +} \ No newline at end of file diff --git a/easytier/src/common/ifcfg/windows.rs b/easytier/src/common/ifcfg/windows.rs index fb4141b1..3d5d27d4 100644 --- a/easytier/src/common/ifcfg/windows.rs +++ b/easytier/src/common/ifcfg/windows.rs @@ -1,61 +1,185 @@ -use std::{io, net::Ipv4Addr}; +use crate::common::ifcfg::win::types::{RouteDataIpv4, RouteDataIpv6}; +use super::win::luid::InterfaceLuid; use async_trait::async_trait; +use cidr::{Ipv4Inet, Ipv6Inet}; +use std::{ + io, + net::{Ipv4Addr, Ipv6Addr}, + ptr::null_mut, +}; +use windows_sys::Win32::{ + Foundation::NO_ERROR, + NetworkManagement::IpHelper::{GetIfEntry, SetIfEntry, MIB_IFROW}, + System::Diagnostics::Debug::{ + FormatMessageW, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS, + }, +}; use winreg::{ enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE}, RegKey, }; -use super::{cidr_to_subnet_mask, run_shell_cmd, Error, IfConfiguerTrait}; - +use super::{Error, IfConfiguerTrait}; pub struct WindowsIfConfiger {} +fn format_win_error(error: u32) -> String { + // use FormatMessageW to get the error message + let mut buffer = vec![0; 1024]; + let size = buffer.len() as u32; + let flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + + unsafe { + FormatMessageW( + flags, + null_mut(), + error, + 0, + buffer.as_mut_ptr() as *mut u16, + size, + null_mut(), + ); + } + let str_end = buffer.iter().position(|&b| b == 0).unwrap_or(buffer.len()); + format!( + "{} (code: {})", + String::from_utf16_lossy(&buffer[..str_end]) + .trim() + .to_string(), + error + ) +} + impl WindowsIfConfiger { pub fn get_interface_index(name: &str) -> Option { crate::arch::windows::find_interface_index(name).ok() } - async fn list_ipv4(name: &str) -> Result, Error> { - use anyhow::Context; - use network_interface::NetworkInterfaceConfig; - use std::net::IpAddr; - let ret = network_interface::NetworkInterface::show().with_context(|| "show interface")?; - let addrs = ret - .iter() - .filter_map(|x| { - if x.name != name { - return None; - } - Some(x.addr.clone()) - }) - .flat_map(|x| x) - .map(|x| x.ip()) - .filter_map(|x| { - if let IpAddr::V4(ipv4) = x { - Some(ipv4) - } else { - None - } - }) - .collect::>(); - - Ok(addrs) + #[tracing::instrument(err, ret)] + async fn add_ip_address(name: &str, addr: Ipv4Inet) -> Result<(), Error> { + let if_index = Self::get_interface_index(name).ok_or(Error::NotFound)?; + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + luid.add_ipv4_address(&addr) + .map_err(|e| anyhow::anyhow!("Failed to add IPv4 address: {}", format_win_error(e)))?; + Ok(()) } - async fn remove_one_ipv4(name: &str, ip: Ipv4Addr) -> Result<(), Error> { - run_shell_cmd( - format!( - "netsh interface ipv4 delete address {} address={}", - name, - ip.to_string() - ) - .as_str(), - ) - .await + #[tracing::instrument(err, ret)] + async fn remove_ip_address(name: &str, addr: Option) -> Result<(), Error> { + let Some(if_index) = Self::get_interface_index(name) else { + return Err(Error::NotFound); + }; + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + if let Some(addr) = addr { + luid.delete_ipv4_address(&addr).map_err(|e| { + anyhow::anyhow!("Failed to delete IPv4 address: {}", format_win_error(e)) + })?; + } else { + luid.flush_ipv4_addresses().map_err(|e| { + anyhow::anyhow!("Failed to flush IPv4 addresses: {}", format_win_error(e)) + })?; + } + Ok(()) + } + + #[tracing::instrument(err, ret)] + async fn set_interface_status(name: &str, up: bool) -> Result<(), Error> { + let Some(if_index) = Self::get_interface_index(name) else { + return Err(Error::NotFound); + }; + + unsafe { + let mut if_row = MIB_IFROW { + wszName: [0; 256], + dwIndex: if_index, + dwType: 0, + dwMtu: 0, + dwSpeed: 0, + dwPhysAddrLen: 0, + bPhysAddr: [0; 8], + dwAdminStatus: if up { 1 } else { 2 }, // 1 = up, 2 = down + dwOperStatus: 0, + dwLastChange: 0, + dwInOctets: 0, + dwInUcastPkts: 0, + dwInNUcastPkts: 0, + dwInDiscards: 0, + dwInErrors: 0, + dwInUnknownProtos: 0, + dwOutOctets: 0, + dwOutUcastPkts: 0, + dwOutNUcastPkts: 0, + dwOutDiscards: 0, + dwOutErrors: 0, + dwOutQLen: 0, + dwDescrLen: 0, + bDescr: [0; 256], + }; + + if GetIfEntry(&mut if_row) == NO_ERROR { + if SetIfEntry(&if_row) == NO_ERROR { + Ok(()) + } else { + Err(anyhow::anyhow!("Failed to set interface status").into()) + } + } else { + Err(anyhow::anyhow!("Failed to get interface entry").into()) + } + } + } + + #[tracing::instrument(err, ret)] + async fn set_interface_mtu(name: &str, mtu: u32) -> Result<(), Error> { + let Some(if_index) = Self::get_interface_index(name) else { + return Err(Error::NotFound); + }; + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + luid.set_iface_config(mtu).map_err(|e| { + anyhow::anyhow!("Failed to set interface config: {}", format_win_error(e)) + })?; + Ok(()) + } + + #[tracing::instrument(err, ret)] + async fn add_ipv6_address(name: &str, addr: Ipv6Inet) -> Result<(), Error> { + let Some(if_index) = Self::get_interface_index(name) else { + return Err(Error::NotFound); + }; + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + luid.add_ipv6_address(&addr) + .map_err(|e| anyhow::anyhow!("Failed to add IPv6 address: {}", format_win_error(e)))?; + Ok(()) + } + + #[tracing::instrument(err, ret)] + async fn remove_ipv6_address(name: &str, addr: Option) -> Result<(), Error> { + let Some(if_index) = Self::get_interface_index(name) else { + return Err(Error::NotFound); + }; + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + if let Some(addr) = addr { + luid.delete_ipv6_address(&addr).map_err(|e| { + anyhow::anyhow!("Failed to delete IPv6 address: {}", format_win_error(e)) + })?; + } else { + luid.flush_ipv6_addresses().map_err(|e| { + anyhow::anyhow!("Failed to flush IPv6 addresses: {}", format_win_error(e)) + })?; + } + Ok(()) } } -#[cfg(target_os = "windows")] #[async_trait] impl IfConfiguerTrait for WindowsIfConfiger { async fn add_ipv4_route( @@ -65,20 +189,20 @@ impl IfConfiguerTrait for WindowsIfConfiger { cidr_prefix: u8, cost: Option, ) -> Result<(), Error> { - let Some(idx) = Self::get_interface_index(name) else { + let Some(if_index) = Self::get_interface_index(name) else { return Err(Error::NotFound); }; - run_shell_cmd( - format!( - "route ADD {} MASK {} 10.1.1.1 IF {} METRIC {}", - address, - cidr_to_subnet_mask(cidr_prefix), - idx, - cost.unwrap_or(9000) - ) - .as_str(), - ) - .await + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + + luid.add_routes_ipv4([RouteDataIpv4 { + destination: Ipv4Inet::new(address, cidr_prefix).unwrap(), + next_hop: Ipv4Addr::UNSPECIFIED, + metric: cost.unwrap_or(9000) as u32, + }]) + .map_err(|e| anyhow::anyhow!("Failed to add route: {}", format_win_error(e)))?; + Ok(()) } async fn remove_ipv4_route( @@ -87,19 +211,18 @@ impl IfConfiguerTrait for WindowsIfConfiger { address: Ipv4Addr, cidr_prefix: u8, ) -> Result<(), Error> { - let Some(idx) = Self::get_interface_index(name) else { + let Some(if_index) = Self::get_interface_index(name) else { return Err(Error::NotFound); }; - run_shell_cmd( - format!( - "route DELETE {} MASK {} IF {}", - address, - cidr_to_subnet_mask(cidr_prefix), - idx - ) - .as_str(), + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + luid.delete_route_ipv4( + &Ipv4Inet::new(address, cidr_prefix).unwrap(), + &Ipv4Addr::UNSPECIFIED, ) - .await + .map_err(|e| anyhow::anyhow!("Failed to delete route: {}", format_win_error(e)))?; + Ok(()) } async fn add_ipv4_ip( @@ -108,39 +231,15 @@ impl IfConfiguerTrait for WindowsIfConfiger { address: Ipv4Addr, cidr_prefix: u8, ) -> Result<(), Error> { - run_shell_cmd( - format!( - "netsh interface ipv4 add address {} address={} mask={}", - name, - address, - cidr_to_subnet_mask(cidr_prefix) - ) - .as_str(), - ) - .await + Self::add_ip_address(name, Ipv4Inet::new(address, cidr_prefix).unwrap()).await } async fn set_link_status(&self, name: &str, up: bool) -> Result<(), Error> { - run_shell_cmd( - format!( - "netsh interface set interface {} {}", - name, - if up { "enable" } else { "disable" } - ) - .as_str(), - ) - .await + Self::set_interface_status(name, up).await } - async fn remove_ip(&self, name: &str, ip: Option) -> Result<(), Error> { - if ip.is_none() { - for ip in Self::list_ipv4(name).await?.iter() { - Self::remove_one_ipv4(name, *ip).await?; - } - Ok(()) - } else { - Self::remove_one_ipv4(name, ip.unwrap()).await - } + async fn remove_ip(&self, name: &str, ip: Option) -> Result<(), Error> { + Self::remove_ip_address(name, ip).await } async fn wait_interface_show(&self, name: &str) -> Result<(), Error> { @@ -160,82 +259,66 @@ impl IfConfiguerTrait for WindowsIfConfiger { } async fn set_mtu(&self, name: &str, mtu: u32) -> Result<(), Error> { - let _ = run_shell_cmd( - format!("netsh interface ipv6 set subinterface {} mtu={}", name, mtu).as_str(), - ) - .await; - run_shell_cmd( - format!("netsh interface ipv4 set subinterface {} mtu={}", name, mtu).as_str(), - ) - .await + Self::set_interface_mtu(name, mtu).await } async fn add_ipv6_ip( &self, name: &str, - address: std::net::Ipv6Addr, + address: Ipv6Addr, cidr_prefix: u8, ) -> Result<(), Error> { - run_shell_cmd( - format!( - "netsh interface ipv6 add address {} {}/{}", - name, address, cidr_prefix - ) - .as_str(), - ) - .await + Self::add_ipv6_address(name, Ipv6Inet::new(address, cidr_prefix).unwrap()).await } - async fn remove_ipv6(&self, name: &str, ip: Option) -> Result<(), Error> { - if let Some(ip) = ip { - run_shell_cmd( - format!("netsh interface ipv6 delete address {} {}", name, ip).as_str(), - ) - .await - } else { - // Remove all IPv6 addresses - run_shell_cmd( - format!("netsh interface ipv6 delete address {} all", name).as_str(), - ) - .await - } + async fn remove_ipv6(&self, name: &str, ip: Option) -> Result<(), Error> { + Self::remove_ipv6_address(name, ip).await } async fn add_ipv6_route( &self, name: &str, - address: std::net::Ipv6Addr, + address: Ipv6Addr, cidr_prefix: u8, cost: Option, ) -> Result<(), Error> { - let cmd = if let Some(cost) = cost { - format!( - "netsh interface ipv6 add route {}/{} {} metric={}", - address, cidr_prefix, name, cost - ) - } else { - format!( - "netsh interface ipv6 add route {}/{} {}", - address, cidr_prefix, name - ) + let Some(if_index) = Self::get_interface_index(name) else { + return Err(Error::NotFound); }; - run_shell_cmd(cmd.as_str()).await + + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + + luid.add_routes_ipv6([RouteDataIpv6 { + destination: Ipv6Inet::new(address, cidr_prefix).unwrap(), + next_hop: Ipv6Addr::UNSPECIFIED, + metric: cost.unwrap_or(9000) as u32, + }]) + .map_err(|e| anyhow::anyhow!("Failed to add route: {}", format_win_error(e)))?; + Ok(()) } async fn remove_ipv6_route( &self, name: &str, - address: std::net::Ipv6Addr, + address: Ipv6Addr, cidr_prefix: u8, ) -> Result<(), Error> { - run_shell_cmd( - format!( - "netsh interface ipv6 delete route {}/{} {}", - address, cidr_prefix, name - ) - .as_str(), + let Some(if_index) = Self::get_interface_index(name) else { + return Err(Error::NotFound); + }; + + let luid = InterfaceLuid::luid_from_index(if_index).map_err(|e| { + anyhow::anyhow!("Failed to get interface luid: {}", format_win_error(e)) + })?; + + luid.delete_route_ipv6( + &Ipv6Inet::new(address, cidr_prefix).unwrap(), + &Ipv6Addr::UNSPECIFIED, ) - .await + .map_err(|e| anyhow::anyhow!("Failed to delete route: {}", format_win_error(e)))?; + Ok(()) } } diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index c6b51ac0..7f23c6bf 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -800,6 +800,7 @@ impl NetworkOptions { if let Some(dev_name) = &self.dev_name { f.dev_name = dev_name.clone() } + println!("mtu: {}, {:?}", f.mtu, self.mtu); if let Some(mtu) = self.mtu { f.mtu = mtu as u32; } diff --git a/easytier/src/instance/virtual_nic.rs b/easytier/src/instance/virtual_nic.rs index 8d022655..898380be 100644 --- a/easytier/src/instance/virtual_nic.rs +++ b/easytier/src/instance/virtual_nic.rs @@ -23,6 +23,7 @@ use crate::{ use byteorder::WriteBytesExt as _; use bytes::{BufMut, BytesMut}; +use cidr::{Ipv4Inet, Ipv6Inet}; use futures::{lock::BiLock, ready, SinkExt, Stream, StreamExt}; use pin_project_lite::pin_project; use pnet::packet::{ipv4::Ipv4Packet, ipv6::Ipv6Packet}; @@ -442,13 +443,13 @@ impl VirtualNic { Ok(()) } - pub async fn remove_ip(&self, ip: Option) -> Result<(), Error> { + pub async fn remove_ip(&self, ip: Option) -> Result<(), Error> { let _g = self.global_ctx.net_ns.guard(); self.ifcfg.remove_ip(self.ifname(), ip).await?; Ok(()) } - pub async fn remove_ipv6(&self, ip: Option) -> Result<(), Error> { + pub async fn remove_ipv6(&self, ip: Option) -> Result<(), Error> { let _g = self.global_ctx.net_ns.guard(); self.ifcfg.remove_ipv6(self.ifname(), ip).await?; Ok(())