diff --git a/.github/update.log b/.github/update.log index 42a5a096d5..5af877296f 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1138,3 +1138,4 @@ Update On Sun Sep 28 20:32:41 CEST 2025 Update On Mon Sep 29 20:38:41 CEST 2025 Update On Tue Sep 30 20:35:53 CEST 2025 Update On Wed Oct 1 20:41:09 CEST 2025 +Update On Thu Oct 2 20:40:58 CEST 2025 diff --git a/clash-nyanpasu/backend/tauri/src/config/nyanpasu/mod.rs b/clash-nyanpasu/backend/tauri/src/config/nyanpasu/mod.rs index 71d8fe9711..f713d8c216 100644 --- a/clash-nyanpasu/backend/tauri/src/config/nyanpasu/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/config/nyanpasu/mod.rs @@ -287,6 +287,11 @@ pub struct IVerge { #[serde(skip_serializing_if = "Option::is_none")] pub network_statistic_widget: Option, + /// PAC URL for automatic proxy configuration + /// This field is used to set PAC proxy without exposing it to the frontend UI + #[serde(skip_serializing_if = "Option::is_none")] + pub pac_url: Option, + /// enable tray text display on Linux systems /// When enabled, shows proxy and TUN mode status as text next to the tray icon /// When disabled, only shows status via icon changes (prevents text display issues on Wayland) diff --git a/clash-nyanpasu/backend/tauri/src/core/mod.rs b/clash-nyanpasu/backend/tauri/src/core/mod.rs index 6db0b7df75..6b52576350 100644 --- a/clash-nyanpasu/backend/tauri/src/core/mod.rs +++ b/clash-nyanpasu/backend/tauri/src/core/mod.rs @@ -4,6 +4,7 @@ pub mod handle; pub mod hotkey; pub mod logger; pub mod manager; +pub mod pac; pub mod service; pub mod storage; pub mod sysopt; diff --git a/clash-nyanpasu/backend/tauri/src/core/pac.rs b/clash-nyanpasu/backend/tauri/src/core/pac.rs new file mode 100644 index 0000000000..aaa61b8fd1 --- /dev/null +++ b/clash-nyanpasu/backend/tauri/src/core/pac.rs @@ -0,0 +1,332 @@ +use crate::{config::Config, log_err}; +use anyhow::{Context, Result}; +use std::{path::PathBuf, time::Duration}; +use sysproxy::Autoproxy; +use tokio::fs; + +/// PAC module for handling Proxy Auto-Configuration +pub struct PacManager; + +// Constants for PAC handling +const PAC_DOWNLOAD_TIMEOUT: u64 = 30; // seconds +const PAC_MAX_RETRIES: u32 = 3; +const PAC_RETRY_DELAY: u64 = 5; // seconds + +impl PacManager { + /// Get PAC URL from config + pub fn get_pac_url() -> Option { + Config::verge().latest().pac_url.clone() + } + + /// Check if PAC is enabled (URL is set) + pub fn is_pac_enabled() -> bool { + Self::get_pac_url().is_some_and(|url| !url.is_empty()) + } + + /// Download PAC script from URL with retry logic + pub async fn download_pac_script(url: &str) -> Result { + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(PAC_DOWNLOAD_TIMEOUT)) + .build() + .context("failed to build HTTP client")?; + + // Retry logic + let mut last_error = None; + for attempt in 1..=PAC_MAX_RETRIES { + match client.get(url).send().await { + Ok(response) => { + if response.status().is_success() { + match response.text().await { + Ok(content) => return Ok(content), + Err(e) => { + let err = + anyhow::anyhow!("failed to read PAC script content: {}", e); + log::warn!(target: "app", "Attempt {}/{} failed: {}", attempt, PAC_MAX_RETRIES, err); + last_error = Some(err); + } + } + } else { + let err = anyhow::anyhow!( + "failed to download PAC script, status: {}", + response.status() + ); + log::warn!(target: "app", "Attempt {}/{} failed: {}", attempt, PAC_MAX_RETRIES, err); + last_error = Some(err); + } + } + Err(e) => { + let err = anyhow::anyhow!("failed to download PAC script: {}", e); + log::warn!(target: "app", "Attempt {}/{} failed: {}", attempt, PAC_MAX_RETRIES, err); + last_error = Some(err); + } + } + + // Wait before retrying (except on last attempt) + if attempt < PAC_MAX_RETRIES { + tokio::time::sleep(Duration::from_secs(PAC_RETRY_DELAY)).await; + } + } + + Err(last_error.unwrap_or_else(|| { + anyhow::anyhow!( + "failed to download PAC script after {} attempts", + PAC_MAX_RETRIES + ) + })) + } + + /// Save PAC script to cache directory + pub async fn save_pac_script(script: &str) -> Result { + let cache_dir = crate::utils::dirs::cache_dir()?; + let pac_file = cache_dir.join("pac.js"); + + fs::write(&pac_file, script) + .await + .context("failed to save PAC script")?; + + Ok(pac_file) + } + + /// Basic validation of PAC script structure - check for required functions + pub async fn validate_pac_script(script: &str) -> Result<()> { + // A basic validation without using the JS engine - just check if FindProxyForURL function exists + if !script.contains("FindProxyForURL") { + return Err(anyhow::anyhow!( + "PAC script must contain FindProxyForURL function" + )); + } + + // Additional basic checks could be added here if needed + Ok(()) + } + + /// Set system proxy to use PAC URL + pub fn set_pac_proxy(url: &str) -> Result<()> { + // Check if Autoproxy is supported on this platform + if !Autoproxy::is_support() { + return Err(anyhow::anyhow!( + "PAC proxy is not supported on this platform" + )); + } + + let autoproxy = Autoproxy { + enable: true, + url: url.to_string(), + }; + + autoproxy + .set_auto_proxy() + .context("failed to set PAC proxy")?; + + Ok(()) + } + + /// Disable PAC proxy and revert to direct proxy + pub fn disable_pac_proxy() -> Result<()> { + // Check if Autoproxy is supported on this platform + if !Autoproxy::is_support() { + log::info!(target: "app", "PAC proxy is not supported on this platform, skipping disable"); + return Ok(()); + } + + let autoproxy = Autoproxy { + enable: false, + url: String::new(), + }; + + autoproxy + .set_auto_proxy() + .context("failed to disable PAC proxy")?; + + Ok(()) + } + + /// Fallback to direct proxy when PAC fails + pub fn fallback_to_direct_proxy() -> Result<()> { + log::warn!(target: "app", "Falling back to direct proxy mode"); + + // Check if Sysproxy is supported on this platform + if !sysproxy::Sysproxy::is_support() { + return Err(anyhow::anyhow!( + "Direct proxy is not supported on this platform" + )); + } + + // Get the standard proxy settings + let port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); + + let (enable, bypass) = { + let verge = Config::verge(); + let verge = verge.latest(); + ( + verge.enable_system_proxy.unwrap_or(false), + verge.system_proxy_bypass.clone(), + ) + }; + + #[cfg(target_os = "windows")] + let default_bypass = "localhost;127.*;192.168.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;"; + #[cfg(target_os = "linux")] + let default_bypass = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1"; + #[cfg(target_os = "macos")] + let default_bypass = "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,"; + + let sysproxy = sysproxy::Sysproxy { + enable, + host: String::from("127.0.0.1"), + port, + bypass: bypass.unwrap_or(default_bypass.into()), + }; + + sysproxy + .set_system_proxy() + .context("failed to set direct proxy as fallback")?; + + log::info!(target: "app", "Fallback to direct proxy successful"); + Ok(()) + } + + /// Update PAC configuration with error handling and fallback + pub async fn update_pac() -> Result<()> { + if !Self::is_pac_enabled() { + log::info!(target: "app", "PAC is not enabled, skipping update"); + return Ok(()); + } + + // Check if Autoproxy is supported on this platform + if !Autoproxy::is_support() { + log::warn!(target: "app", "PAC proxy is not supported on this platform"); + // Try to fallback to direct proxy + log_err!(Self::fallback_to_direct_proxy()); + return Err(anyhow::anyhow!( + "PAC proxy is not supported on this platform" + )); + } + + let pac_url = Self::get_pac_url().unwrap(); + log::info!(target: "app", "Updating PAC from URL: {}", pac_url); + + // Download PAC script + let script = match Self::download_pac_script(&pac_url).await { + Ok(script) => script, + Err(e) => { + log::error!(target: "app", "Failed to download PAC script: {}", e); + // Try to fallback to direct proxy + log_err!(Self::fallback_to_direct_proxy()); + return Err(e); + } + }; + + // Validate PAC script + if let Err(e) = Self::validate_pac_script(&script).await { + log::error!(target: "app", "PAC script validation failed: {}", e); + // Try to fallback to direct proxy + log_err!(Self::fallback_to_direct_proxy()); + return Err(e); + } + + // Save PAC script to cache + if let Err(e) = Self::save_pac_script(&script).await { + log::warn!(target: "app", "Failed to save PAC script to cache: {}", e); + // This is not critical, continue with setting the proxy + } + + // Set system proxy to use PAC + if let Err(e) = Self::set_pac_proxy(&pac_url) { + log::error!(target: "app", "Failed to set PAC proxy: {}", e); + // Try to fallback to direct proxy + log_err!(Self::fallback_to_direct_proxy()); + return Err(e); + } + + log::info!(target: "app", "PAC updated successfully"); + Ok(()) + } + + /// Initialize PAC proxy on startup with error handling + pub async fn init_pac_proxy() -> Result<()> { + if !Self::is_pac_enabled() { + log::info!(target: "app", "PAC is not enabled, skipping initialization"); + return Ok(()); + } + + log::info!(target: "app", "Initializing PAC proxy"); + if let Err(e) = Self::update_pac().await { + log::error!(target: "app", "Failed to initialize PAC proxy: {}", e); + return Err(e); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tokio; + + #[tokio::test] + async fn test_pac_download() { + // Test with a known good PAC URL + let pac_url = "https://raw.githubusercontent.com/Slinetrac/clash-nyanpasu/main/test.pac"; + + match PacManager::download_pac_script(pac_url).await { + Ok(script) => { + assert!(!script.is_empty()); + println!( + "Downloaded PAC script: {}", + &script[..std::cmp::min(100, script.len())] + ); + } + Err(e) => { + eprintln!("Failed to download PAC script: {}", e); + // This might fail in test environment, so we won't assert failure + } + } + } + + #[tokio::test] + async fn test_pac_validation() { + let valid_pac_script = r#" + function FindProxyForURL(url, host) { + return "DIRECT"; + } + "#; + + assert!( + PacManager::validate_pac_script(valid_pac_script) + .await + .is_ok() + ); + + let invalid_pac_script = r#" + function SomeOtherFunction(url, host) { + // This script does not contain the required function + return "PROXY proxy.example.com:8080"; + } + "#; + + assert!( + PacManager::validate_pac_script(invalid_pac_script) + .await + .is_err() + ); + } + + #[tokio::test] + async fn test_pac_save() { + let script = "function FindProxyForURL(url, host) { return 'DIRECT'; }"; + match PacManager::save_pac_script(script).await { + Ok(path) => { + assert!(path.exists()); + // Clean up + let _ = tokio::fs::remove_file(path).await; + } + Err(e) => { + eprintln!("Failed to save PAC script: {}", e); + } + } + } +} diff --git a/clash-nyanpasu/backend/tauri/src/core/sysopt.rs b/clash-nyanpasu/backend/tauri/src/core/sysopt.rs index d1f029b911..9d48459e29 100644 --- a/clash-nyanpasu/backend/tauri/src/core/sysopt.rs +++ b/clash-nyanpasu/backend/tauri/src/core/sysopt.rs @@ -7,6 +7,10 @@ use std::sync::Arc; use sysproxy::Sysproxy; use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe}; +// Import PAC manager +#[cfg(feature = "default-meta")] +use crate::core::pac::PacManager; + #[cfg(target_os = "linux")] use std::process::Command; @@ -66,6 +70,22 @@ impl Sysopt { /// init the sysproxy pub fn init_sysproxy(&self) -> Result<()> { + // Check if PAC is enabled first + #[cfg(feature = "default-meta")] + if PacManager::is_pac_enabled() { + log::info!(target: "app", "Initializing PAC proxy"); + // For PAC, we don't set the regular system proxy + // Instead, we let the PAC manager handle it + tauri::async_runtime::spawn(async { + if let Err(e) = PacManager::init_pac_proxy().await { + log::error!(target: "app", "Failed to initialize PAC proxy: {}", e); + } + }); + // run the system proxy guard + self.guard_proxy(); + return Ok(()); + } + let port = Config::verge() .latest() .verge_mixed_port @@ -89,7 +109,10 @@ impl Sysopt { if enable { let old = Sysproxy::get_system_proxy().ok(); - current.set_system_proxy()?; + if let Err(e) = current.set_system_proxy() { + log::error!(target: "app", "Failed to set system proxy: {}", e); + return Err(e.into()); // Convert sysproxy::Error to anyhow::Error + } *self.old_sysproxy.lock() = old; *self.cur_sysproxy.lock() = Some(current); @@ -102,6 +125,18 @@ impl Sysopt { /// update the system proxy pub fn update_sysproxy(&self) -> Result<()> { + // Check if PAC is enabled first + #[cfg(feature = "default-meta")] + if PacManager::is_pac_enabled() { + log::info!(target: "app", "Updating PAC proxy"); + // For PAC, we don't set the regular system proxy + // Instead, we let the PAC manager handle it + tauri::async_runtime::spawn(async { + log_err!(PacManager::update_pac().await); + }); + return Ok(()); + } + let mut cur_sysproxy = self.cur_sysproxy.lock(); let old_sysproxy = self.old_sysproxy.lock(); @@ -132,6 +167,14 @@ impl Sysopt { /// reset the sysproxy pub fn reset_sysproxy(&self) -> Result<()> { + // Check if PAC is enabled first + #[cfg(feature = "default-meta")] + if PacManager::is_pac_enabled() { + log::info!(target: "app", "Resetting PAC proxy"); + // Disable PAC proxy + log_err!(PacManager::disable_pac_proxy()); + } + let mut cur_sysproxy = self.cur_sysproxy.lock(); let mut old_sysproxy = self.old_sysproxy.lock(); diff --git a/clash-nyanpasu/frontend/interface/src/ipc/bindings.ts b/clash-nyanpasu/frontend/interface/src/ipc/bindings.ts index 0fee3bdd94..6232dbb8a3 100644 --- a/clash-nyanpasu/frontend/interface/src/ipc/bindings.ts +++ b/clash-nyanpasu/frontend/interface/src/ipc/bindings.ts @@ -1068,6 +1068,11 @@ export type IVerge = { * 是否启用网络统计信息浮窗 */ network_statistic_widget?: NetworkStatisticWidgetConfig | null + /** + * PAC URL for automatic proxy configuration + * This field is used to set PAC proxy without exposing it to the frontend UI + */ + pac_url?: string | null /** * enable tray text display on Linux systems * When enabled, shows proxy and TUN mode status as text next to the tray icon diff --git a/lede/package/firmware/linux-firmware/airoha.mk b/lede/package/firmware/linux-firmware/airoha.mk index 627541ba96..9371bc5e86 100644 --- a/lede/package/firmware/linux-firmware/airoha.mk +++ b/lede/package/firmware/linux-firmware/airoha.mk @@ -1,8 +1,21 @@ -Package/en8811h-firmware = $(call Package/firmware-default,Airoha EN8811H PHY firmware) -define Package/en8811h-firmware/install +Package/airoha-en8811h-firmware = $(call Package/firmware-default,Airoha EN8811H 2.5G Ethernet PHY firmware) +define Package/airoha-en8811h-firmware/install $(INSTALL_DIR) $(1)/lib/firmware/airoha $(CP) \ - $(PKG_BUILD_DIR)/airoha/*.bin \ + $(PKG_BUILD_DIR)/airoha/EthMD32.dm.bin \ + $(PKG_BUILD_DIR)/airoha/EthMD32.DSP.bin \ $(1)/lib/firmware/airoha endef -$(eval $(call BuildPackage,en8811h-firmware)) + +$(eval $(call BuildPackage,airoha-en8811h-firmware)) + +Package/airoha-en7581-npu-firmware = $(call Package/firmware-default,Airoha EN7581 NPU firmware) +define Package/airoha-en7581-npu-firmware/install + $(INSTALL_DIR) $(1)/lib/firmware/airoha + $(CP) \ + $(PKG_BUILD_DIR)/airoha/en7581_npu_data.bin \ + $(PKG_BUILD_DIR)/airoha/en7581_npu_rv32.bin \ + $(1)/lib/firmware/airoha +endef + +$(eval $(call BuildPackage,airoha-en7581-npu-firmware)) diff --git a/small/hysteria/Makefile b/small/hysteria/Makefile index b488e9ec73..325fe40fe9 100644 --- a/small/hysteria/Makefile +++ b/small/hysteria/Makefile @@ -5,12 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=hysteria -PKG_VERSION:=2.6.3 +PKG_VERSION:=2.6.4 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/app/v$(PKG_VERSION)? -PKG_HASH:=bed1ece93dfaa07fbf709136efadaf4ccb09e0375844de3e28c5644ebe518eb0 +PKG_HASH:=9d989174736654f8608a4ba7c1c49306e4b97c6a19d65d926c10a8521de7b2be PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-app-v$(PKG_VERSION) PKG_LICENSE:=MIT diff --git a/v2ray-core/common/environment/rootcap_impl.go b/v2ray-core/common/environment/rootcap_impl.go index 55e01ce191..b81da82d82 100644 --- a/v2ray-core/common/environment/rootcap_impl.go +++ b/v2ray-core/common/environment/rootcap_impl.go @@ -74,7 +74,8 @@ func (r *rootEnvImpl) DropProxyEnvironment(tag string) error { if err != nil { return err } - return transientStorage.DropScope(r.ctx, tag) + transientStorage.Clear(r.ctx) + return r.transientStorage.DropScope(r.ctx, tag) } type appEnvImpl struct { diff --git a/v2ray-core/common/environment/transientstorageimpl/storage.go b/v2ray-core/common/environment/transientstorageimpl/storage.go index c349ef7ba8..29e543c1e5 100644 --- a/v2ray-core/common/environment/transientstorageimpl/storage.go +++ b/v2ray-core/common/environment/transientstorageimpl/storage.go @@ -56,7 +56,16 @@ func (s *scopedTransientStorageImpl) List(ctx context.Context, keyPrefix string) func (s *scopedTransientStorageImpl) Clear(ctx context.Context) { s.access.Lock() defer s.access.Unlock() + for _, v := range s.values { + if sw, ok := v.(storage.TransientStorageLifecycleReceiver); ok { + _ = sw.Close() + } + } s.values = map[string]interface{}{} + for _, v := range s.scopes { + v.Clear(ctx) + } + s.scopes = map[string]storage.ScopedTransientStorage{} } func (s *scopedTransientStorageImpl) NarrowScope(ctx context.Context, key string) (storage.ScopedTransientStorage, error) { @@ -74,6 +83,9 @@ func (s *scopedTransientStorageImpl) NarrowScope(ctx context.Context, key string func (s *scopedTransientStorageImpl) DropScope(ctx context.Context, key string) error { s.access.Lock() defer s.access.Unlock() + if v, ok := s.scopes[key]; ok { + v.Clear(ctx) + } delete(s.scopes, key) return nil } diff --git a/v2ray-core/features/extension/storage/storage.go b/v2ray-core/features/extension/storage/storage.go index 03f061c403..6217480e27 100644 --- a/v2ray-core/features/extension/storage/storage.go +++ b/v2ray-core/features/extension/storage/storage.go @@ -3,6 +3,7 @@ package storage import ( "context" + "github.com/v2fly/v2ray-core/v5/common" "github.com/v2fly/v2ray-core/v5/features" ) @@ -32,3 +33,8 @@ type ScopedPersistentStorageService interface { } var ScopedPersistentStorageServiceType = (*ScopedPersistentStorageService)(nil) + +type TransientStorageLifecycleReceiver interface { + IsTransientStorageLifecycleReceiver() + common.Closable +} diff --git a/v2ray-core/go.mod b/v2ray-core/go.mod index 3b01404007..7731768a1a 100644 --- a/v2ray-core/go.mod +++ b/v2ray-core/go.mod @@ -24,7 +24,7 @@ require ( github.com/pion/dtls/v2 v2.2.12 github.com/pion/transport/v2 v2.2.10 github.com/pires/go-proxyproto v0.8.1 - github.com/quic-go/quic-go v0.54.0 + github.com/quic-go/quic-go v0.54.1 github.com/refraction-networking/utls v1.8.0 github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb github.com/stretchr/testify v1.11.1 diff --git a/v2ray-core/go.sum b/v2ray-core/go.sum index 1edc62709c..edccae1872 100644 --- a/v2ray-core/go.sum +++ b/v2ray-core/go.sum @@ -440,8 +440,8 @@ github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg= +github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE= github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= diff --git a/v2ray-core/transport/internet/grpc/dial.go b/v2ray-core/transport/internet/grpc/dial.go index a713d6043a..4154552e58 100644 --- a/v2ray-core/transport/internet/grpc/dial.go +++ b/v2ray-core/transport/internet/grpc/dial.go @@ -17,6 +17,8 @@ import ( core "github.com/v2fly/v2ray-core/v5" "github.com/v2fly/v2ray-core/v5/common" + "github.com/v2fly/v2ray-core/v5/common/environment" + "github.com/v2fly/v2ray-core/v5/common/environment/envctx" "github.com/v2fly/v2ray-core/v5/common/net" "github.com/v2fly/v2ray-core/v5/common/session" "github.com/v2fly/v2ray-core/v5/transport/internet" @@ -38,12 +40,25 @@ func init() { common.Must(internet.RegisterTransportDialer(protocolName, Dial)) } -type dialerCanceller func() +type transportConnectionState struct { + scopedDialerMap map[net.Destination]*grpc.ClientConn + scopedDialerAccess sync.Mutex +} -var ( - globalDialerMap map[net.Destination]*grpc.ClientConn - globalDialerAccess sync.Mutex -) +func (t *transportConnectionState) IsTransientStorageLifecycleReceiver() { +} + +func (t *transportConnectionState) Close() error { + t.scopedDialerAccess.Lock() + defer t.scopedDialerAccess.Unlock() + for _, conn := range t.scopedDialerMap { + _ = conn.Close() + } + t.scopedDialerMap = nil + return nil +} + +type dialerCanceller func() func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) { grpcSettings := streamSettings.ProtocolSettings.(*Config) @@ -70,25 +85,36 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne } func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.DialOption, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, dialerCanceller, error) { - globalDialerAccess.Lock() - defer globalDialerAccess.Unlock() + transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment) + state, err := transportEnvironment.TransientStorage().Get(ctx, "grpc-transport-connection-state") + if err != nil { + state = &transportConnectionState{} + transportEnvironment.TransientStorage().Put(ctx, "grpc-transport-connection-state", state) + state, err = transportEnvironment.TransientStorage().Get(ctx, "grpc-transport-connection-state") + if err != nil { + return nil, nil, newError("failed to get grpc transport connection state").Base(err) + } + } + stateTyped := state.(*transportConnectionState) - if globalDialerMap == nil { - globalDialerMap = make(map[net.Destination]*grpc.ClientConn) + stateTyped.scopedDialerAccess.Lock() + defer stateTyped.scopedDialerAccess.Unlock() + + if stateTyped.scopedDialerMap == nil { + stateTyped.scopedDialerMap = make(map[net.Destination]*grpc.ClientConn) } canceller := func() { - globalDialerAccess.Lock() - defer globalDialerAccess.Unlock() - delete(globalDialerMap, dest) + stateTyped.scopedDialerAccess.Lock() + defer stateTyped.scopedDialerAccess.Unlock() + delete(stateTyped.scopedDialerMap, dest) } - // TODO Should support chain proxy to the same destination - if client, found := globalDialerMap[dest]; found && client.GetState() != connectivity.Shutdown { + if client, found := stateTyped.scopedDialerMap[dest]; found && client.GetState() != connectivity.Shutdown { return client, canceller, nil } - conn, err := grpc.Dial( + conn, err := grpc.NewClient( dest.Address.String()+":"+dest.Port.String(), dialOption, grpc.WithConnectParams(grpc.ConnectParams{ @@ -117,6 +143,14 @@ func getGrpcClient(ctx context.Context, dest net.Destination, dialOption grpc.Di return internet.DialSystem(detachedContext, net.TCPDestination(address, port), streamSettings.SocketSettings) }), ) - globalDialerMap[dest] = conn + canceller = func() { + stateTyped.scopedDialerAccess.Lock() + defer stateTyped.scopedDialerAccess.Unlock() + delete(stateTyped.scopedDialerMap, dest) + if err != nil { + conn.Close() + } + } + stateTyped.scopedDialerMap[dest] = conn return conn, canceller, err } diff --git a/v2rayn/v2rayN/ServiceLib/Common/ProcUtils.cs b/v2rayn/v2rayN/ServiceLib/Common/ProcUtils.cs index 561278ce0a..2819f2fb51 100644 --- a/v2rayn/v2rayN/ServiceLib/Common/ProcUtils.cs +++ b/v2rayn/v2rayN/ServiceLib/Common/ProcUtils.cs @@ -67,116 +67,4 @@ public static class ProcUtils Logging.SaveLog(_tag, ex); } } - - public static async Task ProcessKill(int pid) - { - try - { - await ProcessKill(Process.GetProcessById(pid), false); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - public static async Task ProcessKill(Process? proc, bool review) - { - if (proc is null) - { - return; - } - - GetProcessKeyInfo(proc, review, out var procId, out var fileName, out var processName); - - try - { - if (Utils.IsNonWindows()) - { - proc?.Kill(true); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - try - { - proc?.Kill(); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - try - { - proc?.Close(); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - try - { - proc?.Dispose(); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - await Task.Delay(300); - await ProcessKillByKeyInfo(review, procId, fileName, processName); - } - - private static void GetProcessKeyInfo(Process? proc, bool review, out int? procId, out string? fileName, out string? processName) - { - procId = null; - fileName = null; - processName = null; - if (!review) - { - return; - } - try - { - procId = proc?.Id; - fileName = proc?.MainModule?.FileName; - processName = proc?.ProcessName; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - - private static async Task ProcessKillByKeyInfo(bool review, int? procId, string? fileName, string? processName) - { - if (review && procId != null && fileName != null) - { - try - { - var lstProc = Process.GetProcessesByName(processName); - foreach (var proc2 in lstProc) - { - if (proc2.Id == procId) - { - Logging.SaveLog($"{_tag}, KillProcess not completing the job, procId"); - await ProcessKill(proc2, false); - } - if (proc2.MainModule != null && proc2.MainModule?.FileName == fileName) - { - Logging.SaveLog($"{_tag}, KillProcess not completing the job, fileName"); - } - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - } - } } diff --git a/v2rayn/v2rayN/ServiceLib/Common/Job.cs b/v2rayn/v2rayN/ServiceLib/Common/WindowsJob.cs similarity index 98% rename from v2rayn/v2rayN/ServiceLib/Common/Job.cs rename to v2rayn/v2rayN/ServiceLib/Common/WindowsJob.cs index fe968d2d05..f7fd2d74af 100644 --- a/v2rayn/v2rayN/ServiceLib/Common/Job.cs +++ b/v2rayn/v2rayN/ServiceLib/Common/WindowsJob.cs @@ -7,11 +7,11 @@ namespace ServiceLib.Common; * http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net */ - public sealed class Job : IDisposable + public sealed class WindowsJob : IDisposable { private IntPtr handle = IntPtr.Zero; - public Job() + public WindowsJob() { handle = CreateJobObject(IntPtr.Zero, null); var extendedInfoPtr = IntPtr.Zero; @@ -94,7 +94,7 @@ namespace ServiceLib.Common; } } - ~Job() + ~WindowsJob() { Dispose(false); } diff --git a/v2rayn/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayn/v2rayN/ServiceLib/Manager/AppManager.cs index 2f5ccb64d8..16aa425375 100644 --- a/v2rayn/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayn/v2rayN/ServiceLib/Manager/AppManager.cs @@ -8,7 +8,7 @@ public sealed class AppManager private Config _config; private int? _statePort; private int? _statePort2; - private Job? _processJob; + private WindowsJob? _processJob; public static AppManager Instance => _instance.Value; public Config Config => _config; diff --git a/v2rayn/v2rayN/ServiceLib/Services/ProcessService.cs b/v2rayn/v2rayN/ServiceLib/Services/ProcessService.cs index db3a95acf7..06e27d5236 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/ProcessService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/ProcessService.cs @@ -178,5 +178,6 @@ public class ProcessService : IDisposable } _isDisposed = true; + GC.SuppressFinalize(this); } }